diff --git a/src/X1.Application/Features/DeviceRuntimes/Queries/GetDeviceRuntimes/GetDeviceRuntimesQueryHandler.cs b/src/X1.Application/Features/DeviceRuntimes/Queries/GetDeviceRuntimes/GetDeviceRuntimesQueryHandler.cs index 1c17ce6..bc9ecc1 100644 --- a/src/X1.Application/Features/DeviceRuntimes/Queries/GetDeviceRuntimes/GetDeviceRuntimesQueryHandler.cs +++ b/src/X1.Application/Features/DeviceRuntimes/Queries/GetDeviceRuntimes/GetDeviceRuntimesQueryHandler.cs @@ -73,25 +73,11 @@ public class GetDeviceRuntimesQueryHandler : IRequestHandler public class DeviceRuntimeDto { - /// - /// 运行时状态ID - /// - public string Id { get; set; } = null!; - /// /// 设备编号 /// @@ -49,34 +44,22 @@ public class DeviceRuntimeDto /// /// 运行时状态 /// - public string RuntimeStatus { get; set; } = null!; - - /// - /// 运行编码 - /// - public string? RuntimeCode { get; set; } + public int RuntimeStatus { get; set; } /// /// 网络栈配置编号 /// public string? NetworkStackCode { get; set; } - - /// - /// 创建时间 - /// - public DateTime CreatedAt { get; set; } - - /// - /// 更新时间 + /// 设备名称 /// - public DateTime UpdatedAt { get; set; } + public string? Name { get; set; } /// - /// 设备信息 + /// 创建时间 /// - public DeviceInfoDto? Device { get; set; } + public DateTime CreatedAt { get; set; } } /// diff --git a/src/X1.Domain/Entities/Device/CellularDeviceRuntimeDetail.cs b/src/X1.Domain/Entities/Device/CellularDeviceRuntimeDetail.cs index a32fc47..c8ecb3a 100644 --- a/src/X1.Domain/Entities/Device/CellularDeviceRuntimeDetail.cs +++ b/src/X1.Domain/Entities/Device/CellularDeviceRuntimeDetail.cs @@ -10,6 +10,13 @@ public class CellularDeviceRuntimeDetail : Entity { private CellularDeviceRuntimeDetail() { } + /// + /// 设备编码 + /// + [Required] + [MaxLength(50)] + public string DeviceCode { get; private set; } = null!; + /// /// 运行编码 /// @@ -17,6 +24,13 @@ public class CellularDeviceRuntimeDetail : Entity [MaxLength(50)] public string RuntimeCode { get; private set; } = null!; + /// + /// 网络栈配置编号 + /// + [Required] + [MaxLength(50)] + public string NetworkStackCode { get; private set; } = null!; + /// /// 运行时状态(true=运行中,false=已停止) /// @@ -37,14 +51,18 @@ public class CellularDeviceRuntimeDetail : Entity /// 创建设备运行时明细记录 /// public static CellularDeviceRuntimeDetail Create( + string deviceCode, string runtimeCode, + string networkStackCode, string createdBy, bool runtimeStatus = false) { var detail = new CellularDeviceRuntimeDetail { Id = Guid.NewGuid().ToString(), + DeviceCode = deviceCode, RuntimeCode = runtimeCode, + NetworkStackCode = networkStackCode, RuntimeStatus = runtimeStatus, CreatedBy = createdBy, CreatedAt = DateTime.UtcNow diff --git a/src/X1.Domain/Entities/Device/CellularDeviceRuntimeHistory.cs b/src/X1.Domain/Entities/Device/CellularDeviceRuntimeHistory.cs deleted file mode 100644 index 59c843c..0000000 --- a/src/X1.Domain/Entities/Device/CellularDeviceRuntimeHistory.cs +++ /dev/null @@ -1,106 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; -using CellularManagement.Domain.Entities.Common; - -namespace CellularManagement.Domain.Entities.Device; - -/// -/// 蜂窝设备运行时历史记录实体 -/// -public class CellularDeviceRuntimeHistory : BaseEntity -{ - private CellularDeviceRuntimeHistory() { } - - /// - /// 运行时ID(外键) - /// - [Required] - [MaxLength(450)] - public string RuntimeId { get; private set; } = null!; - - /// - /// 设备编号 - /// - [Required] - [MaxLength(50)] - public string DeviceCode { get; private set; } = null!; - - /// - /// 操作前状态 - /// - public DeviceRuntimeStatus PreviousStatus { get; private set; } - - /// - /// 操作后状态 - /// - public DeviceRuntimeStatus NewStatus { get; private set; } - - /// - /// 运行编码 - /// - [MaxLength(50)] - public string? RuntimeCode { get; private set; } - - /// - /// 网络栈配置编号 - /// - [MaxLength(50)] - public string? NetworkStackCode { get; private set; } - - /// - /// 操作人ID - /// - [Required] - [MaxLength(450)] - public string OperatorId { get; private set; } = null!; - - /// - /// 操作描述 - /// - [Required] - [MaxLength(200)] - public string OperationDescription { get; private set; } = null!; - - /// - /// 操作时间 - /// - public DateTime OperationAt { get; private set; } - - /// - /// 运行时实例 - /// - public virtual CellularDeviceRuntime Runtime { get; private set; } = null!; - - /// - /// 创建设备运行时历史记录 - /// - public static CellularDeviceRuntimeHistory Create( - string runtimeId, - string deviceCode, - DeviceRuntimeStatus previousStatus, - DeviceRuntimeStatus newStatus, - string? runtimeCode, - string? networkStackCode, - string operatorId, - string operationDescription, - DateTime operationAt) - { - var history = new CellularDeviceRuntimeHistory - { - Id = Guid.NewGuid().ToString(), - RuntimeId = runtimeId, - DeviceCode = deviceCode, - PreviousStatus = previousStatus, - NewStatus = newStatus, - RuntimeCode = runtimeCode, - NetworkStackCode = networkStackCode, - OperatorId = operatorId, - OperationDescription = operationDescription, - OperationAt = operationAt, - CreatedAt = DateTime.UtcNow, - UpdatedAt = DateTime.UtcNow - }; - - return history; - } -} \ No newline at end of file diff --git a/src/X1.Infrastructure/Repositories/Device/CellularDeviceRuntimeRepository.cs b/src/X1.Infrastructure/Repositories/Device/CellularDeviceRuntimeRepository.cs index 4667019..1623fbc 100644 --- a/src/X1.Infrastructure/Repositories/Device/CellularDeviceRuntimeRepository.cs +++ b/src/X1.Infrastructure/Repositories/Device/CellularDeviceRuntimeRepository.cs @@ -151,7 +151,10 @@ public class CellularDeviceRuntimeRepository : BaseRepository true, cancellationToken: cancellationToken); + var query = await QueryRepository.FindAsync( + r => true, + include: q => q.Include(r => r.Device), + cancellationToken: cancellationToken); if (!string.IsNullOrWhiteSpace(keyword)) { @@ -184,8 +187,13 @@ public class CellularDeviceRuntimeRepository : BaseRepository q.Include(r => r.Device), + cancellationToken); return (result.TotalCount, result.Items.ToList()); } diff --git a/src/X1.WebUI/src/constants/api.ts b/src/X1.WebUI/src/constants/api.ts index 1a84b89..1493abb 100644 --- a/src/X1.WebUI/src/constants/api.ts +++ b/src/X1.WebUI/src/constants/api.ts @@ -2,6 +2,7 @@ export const API_PATHS = { // 设备相关 DEVICES: '/devices', + DEVICE_RUNTIMES: '/api/device-runtimes', // 协议相关 PROTOCOLS: '/protocolversions', @@ -21,6 +22,8 @@ export const API_PATHS = { // 用户相关 USERS: '/users', ROLES: '/roles', + PERMISSIONS: '/api/permissions', + ROLE_PERMISSIONS: '/api/role-permissions', // 认证相关 AUTH: { diff --git a/src/X1.WebUI/src/constants/menuConfig.ts b/src/X1.WebUI/src/constants/menuConfig.ts index 56fd981..0c896cd 100644 --- a/src/X1.WebUI/src/constants/menuConfig.ts +++ b/src/X1.WebUI/src/constants/menuConfig.ts @@ -54,6 +54,9 @@ export type Permission = | 'corenetworkconfigs.manage' | 'networkstackconfigs.view' | 'networkstackconfigs.manage' + // 设备运行时管理权限 + | 'deviceruntimes.view' + | 'deviceruntimes.manage' export interface MenuItem { @@ -187,6 +190,11 @@ export const menuItems: MenuItem[] = [ title: '协议列表', href: '/dashboard/instruments/protocols', permission: 'protocols.view', + }, + { + title: '运行时状态', + href: '/dashboard/instruments/device-runtimes/list', + permission: 'deviceruntimes.view', } ], }, diff --git a/src/X1.WebUI/src/contexts/AuthContext.tsx b/src/X1.WebUI/src/contexts/AuthContext.tsx index b0b2c06..1033fa5 100644 --- a/src/X1.WebUI/src/contexts/AuthContext.tsx +++ b/src/X1.WebUI/src/contexts/AuthContext.tsx @@ -81,6 +81,9 @@ const getDefaultPermissions = (userPermissions: Record = {}) => 'corenetworkconfigs.manage', 'networkstackconfigs.view', 'networkstackconfigs.manage', + // 设备运行时管理权限 + 'deviceruntimes.view', + 'deviceruntimes.manage', ]) ]; diff --git a/src/X1.WebUI/src/pages/device-runtimes/DeviceRuntimeDetail.tsx b/src/X1.WebUI/src/pages/device-runtimes/DeviceRuntimeDetail.tsx new file mode 100644 index 0000000..d3bbf67 --- /dev/null +++ b/src/X1.WebUI/src/pages/device-runtimes/DeviceRuntimeDetail.tsx @@ -0,0 +1,276 @@ +import React, { useState, useEffect } from 'react'; +import { useParams, useNavigate } from 'react-router-dom'; +import { + deviceRuntimeService, + GetDeviceRuntimeStatusResponse +} from '@/services/deviceRuntimeService'; +import { Button } from '@/components/ui/button'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Badge } from '@/components/ui/badge'; +import { Separator } from '@/components/ui/separator'; +import { useToast } from '@/components/ui/use-toast'; +import { ArrowLeftIcon, ReloadIcon } from '@radix-ui/react-icons'; +import { format } from 'date-fns'; +import { cn } from '@/lib/utils'; + +export default function DeviceRuntimeDetail() { + const { deviceCode } = useParams<{ deviceCode: string }>(); + const navigate = useNavigate(); + const { toast } = useToast(); + + const [deviceRuntime, setDeviceRuntime] = useState(null); + const [loading, setLoading] = useState(false); + const [refreshing, setRefreshing] = useState(false); + + // 获取设备运行时状态详情 + const fetchDeviceRuntimeStatus = async (isRefresh = false) => { + if (!deviceCode) return; + + if (isRefresh) { + setRefreshing(true); + } else { + setLoading(true); + } + + try { + const result = await deviceRuntimeService.getDeviceRuntimeStatus(deviceCode); + + if (result.isSuccess && result.data) { + setDeviceRuntime(result.data); + } else { + toast({ + title: '获取设备运行时状态失败', + description: result.errorMessages?.join(', ') || '未知错误', + variant: 'destructive', + }); + } + } catch (error) { + console.error('获取设备运行时状态失败:', error); + toast({ + title: '获取设备运行时状态失败', + description: '网络错误或服务器异常', + variant: 'destructive', + }); + } finally { + setLoading(false); + setRefreshing(false); + } + }; + + // 格式化时间 + const formatDateTime = (dateString: string) => { + try { + return format(new Date(dateString), 'yyyy-MM-dd HH:mm:ss'); + } catch { + return dateString; + } + }; + + // 获取状态描述和颜色 + const getStatusInfo = (status: string) => { + const description = deviceRuntimeService.getRuntimeStatusDescription(status); + const color = deviceRuntimeService.getRuntimeStatusColor(status); + return { description, color }; + }; + + // 初始化加载 + useEffect(() => { + fetchDeviceRuntimeStatus(); + }, [deviceCode]); + + if (loading) { + return ( +
+
+
+ 加载中... +
+
+ ); + } + + if (!deviceRuntime) { + return ( +
+
+

设备运行时状态不存在

+

未找到设备编号为 {deviceCode} 的运行时状态

+ +
+
+ ); + } + + const statusInfo = getStatusInfo(deviceRuntime.runtimeStatus); + + return ( +
+ {/* 页面头部 */} +
+
+ +
+

设备运行时详情

+

设备编号: {deviceRuntime.deviceCode}

+
+
+ +
+ + {/* 状态概览卡片 */} + + + + 运行时状态 + + {statusInfo.description} + + + + +
+
+
设备编号
+
{deviceRuntime.deviceCode}
+
+
+
运行编码
+
+ {deviceRuntime.runtimeCode || '-'} +
+
+
+
+
+ + {/* 详细信息卡片 */} +
+ {/* 网络配置信息 */} + + + 网络配置 + + +
+
网络栈配置编号
+
+ {deviceRuntime.networkStackCode || '-'} +
+
+ +
+
运行时状态
+
+ + {statusInfo.description} + +
+
+
+
+ + {/* 时间信息 */} + + + 时间信息 + + +
+
最后更新时间
+
+ {deviceRuntime.updatedAt ? formatDateTime(deviceRuntime.updatedAt) : '-'} +
+
+ +
+
状态持续时间
+
+ {deviceRuntime.updatedAt ? ( + + {(() => { + const now = new Date(); + const updatedAt = new Date(deviceRuntime.updatedAt); + const diffMs = now.getTime() - updatedAt.getTime(); + const diffHours = Math.floor(diffMs / (1000 * 60 * 60)); + const diffMinutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60)); + + if (diffHours > 0) { + return `${diffHours}小时${diffMinutes}分钟`; + } else { + return `${diffMinutes}分钟`; + } + })()} + + ) : '-'} +
+
+
+
+
+ + {/* 操作按钮 */} + + + 操作 + + +
+ + +
+
+
+
+ ); +} \ No newline at end of file diff --git a/src/X1.WebUI/src/pages/device-runtimes/DeviceRuntimesTable.tsx b/src/X1.WebUI/src/pages/device-runtimes/DeviceRuntimesTable.tsx new file mode 100644 index 0000000..1385329 --- /dev/null +++ b/src/X1.WebUI/src/pages/device-runtimes/DeviceRuntimesTable.tsx @@ -0,0 +1,209 @@ +import React from 'react'; +import { DeviceRuntime } from '@/services/deviceRuntimeService'; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'; +import { Button } from '@/components/ui/button'; +import { Badge } from '@/components/ui/badge'; +import { Checkbox } from '@/components/ui/checkbox'; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger +} from '@/components/ui/dropdown-menu'; +import { MoreHorizontalIcon, PlayIcon, StopIcon } from '@radix-ui/react-icons'; +import { format } from 'date-fns'; +import { cn } from '@/lib/utils'; + +interface DeviceRuntimesTableProps { + deviceRuntimes: DeviceRuntime[]; + loading: boolean; + columns: { key: string; title: string; visible: boolean }[]; + density: 'compact' | 'default' | 'comfortable'; + selectedDevices: string[]; + onDeviceSelect: (deviceCode: string, checked: boolean) => void; + onSelectAll: (checked: boolean) => void; + onStopDevice: (deviceCode: string) => void; + getRuntimeStatusDescription: (status: number | string) => string; + getRuntimeStatusColor: (status: number | string) => string; +} + +export default function DeviceRuntimesTable({ + deviceRuntimes, + loading, + columns, + density, + selectedDevices, + onDeviceSelect, + onSelectAll, + onStopDevice, + getRuntimeStatusDescription, + getRuntimeStatusColor, +}: DeviceRuntimesTableProps) { + // 密度样式映射 + const densityStyles = { + compact: 'py-1', + default: 'py-2', + comfortable: 'py-3', + }; + + // 格式化时间 + const formatDateTime = (dateString: string) => { + try { + return format(new Date(dateString), 'yyyy-MM-dd HH:mm:ss'); + } catch { + return dateString; + } + }; + + // 渲染单元格内容 + const renderCell = (device: DeviceRuntime, columnKey: string) => { + switch (columnKey) { + case 'deviceCode': + return ( +
+ {device.deviceCode} +
+ ); + + case 'name': + return ( +
+ {device.name || '-'} +
+ ); + + case 'runtimeStatus': + const statusDescription = getRuntimeStatusDescription(device.runtimeStatus); + const statusColor = getRuntimeStatusColor(device.runtimeStatus); + return ( + + {statusDescription} + + ); + + case 'networkStackCode': + return ( +
+ {device.networkStackCode || '-'} +
+ ); + + case 'createdAt': + return ( +
+ {formatDateTime(device.createdAt)} +
+ ); + + case 'actions': + return ( + + + + + + {device.runtimeStatus === 1 && ( + onStopDevice(device.deviceCode)} + className="text-red-600 focus:text-red-600" + > + + 停止设备 + + )} + {device.runtimeStatus !== 1 && ( + + + 设备未运行 + + )} + + + ); + + default: + return null; + } + }; + + // 过滤可见列 + const visibleColumns = columns.filter(col => col.visible); + + return ( +
+ + + + {/* 选择框列 */} + + 0} + onCheckedChange={onSelectAll} + aria-label="全选" + /> + + + {/* 动态列 */} + {visibleColumns.map((column) => ( + + {column.title} + + ))} + + + + {loading ? ( + + +
+
+ 加载中... +
+
+
+ ) : deviceRuntimes.length === 0 ? ( + + +
+ 暂无设备运行时数据 +
+
+
+ ) : ( + deviceRuntimes.map((device) => ( + + {/* 选择框 */} + + onDeviceSelect(device.deviceCode, checked as boolean)} + aria-label={`选择设备 ${device.deviceCode}`} + /> + + + {/* 动态单元格 */} + {visibleColumns.map((column) => ( + + {renderCell(device, column.key)} + + ))} + + )) + )} +
+
+
+ ); +} \ No newline at end of file diff --git a/src/X1.WebUI/src/pages/device-runtimes/DeviceRuntimesView.tsx b/src/X1.WebUI/src/pages/device-runtimes/DeviceRuntimesView.tsx new file mode 100644 index 0000000..0e174e3 --- /dev/null +++ b/src/X1.WebUI/src/pages/device-runtimes/DeviceRuntimesView.tsx @@ -0,0 +1,507 @@ +import React, { useState, useEffect } from 'react'; +import { + getDeviceRuntimes, + DeviceRuntime, + GetDeviceRuntimesRequest, + startDevices, + stopDevice, + StartDeviceRequest, + deviceRuntimeService +} from '@/services/deviceRuntimeService'; +import DeviceRuntimesTable from './DeviceRuntimesTable'; +import { Input } from '@/components/ui/input'; +import PaginationBar from '@/components/ui/PaginationBar'; +import TableToolbar, { DensityType } from '@/components/ui/TableToolbar'; +import { Button } from '@/components/ui/button'; +import { ChevronDownIcon, ChevronUpIcon } from '@radix-ui/react-icons'; +import { useToast } from '@/components/ui/use-toast'; +import { Badge } from '@/components/ui/badge'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogTrigger +} from '@/components/ui/dialog'; +import { Label } from '@/components/ui/label'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; +import { Checkbox } from '@/components/ui/checkbox'; + +const defaultColumns = [ + { key: 'deviceCode', title: '设备编号', visible: true }, + { key: 'name', title: '设备名称', visible: true }, + { key: 'runtimeStatus', title: '运行时状态', visible: true }, + { key: 'networkStackCode', title: '网络栈配置', visible: true }, + { key: 'createdAt', title: '创建时间', visible: true }, + { key: 'actions', title: '操作', visible: true }, +]; + +// 字段类型声明 +type SearchField = + | { key: string; label: string; type: 'input'; placeholder: string } + | { key: string; label: string; type: 'select'; options: { value: string; label: string }[] }; + +// 第一行字段(收起时只显示这3个) +const firstRowFields: SearchField[] = [ + { key: 'searchTerm', label: '搜索关键词', type: 'input', placeholder: '请输入设备编号或设备名称' }, + { key: 'runtimeStatus', label: '运行时状态', type: 'select', options: [ + { value: '', label: '全部状态' }, + { value: '0', label: '初始化' }, + { value: '1', label: '运行中' }, + { value: '2', label: '已停止' }, + { value: '3', label: '错误' }, + ] }, +]; + +// 高级字段(展开时才显示) +const advancedFields: SearchField[] = [ + { key: 'pageSize', label: '每页数量', type: 'select', options: [ + { value: '10', label: '10条/页' }, + { value: '20', label: '20条/页' }, + { value: '50', label: '50条/页' }, + { value: '100', label: '100条/页' }, + ] }, +]; + +/** + * 设备运行时管理页面 + * + * 表格字段映射关系(基于后端DeviceRuntimeDto): + * - deviceCode: 设备编号 + * - name: 设备名称 + * - runtimeStatus: 运行时状态(0:初始化, 1:运行中, 2:已停止, 3:错误) + * - networkStackCode: 网络栈配置编号 + * - createdAt: 创建时间 + */ + +export default function DeviceRuntimesView() { + const [deviceRuntimes, setDeviceRuntimes] = useState([]); + const [loading, setLoading] = useState(false); + const [total, setTotal] = useState(0); + const [pageNumber, setPageNumber] = useState(1); + const [pageSize, setPageSize] = useState(10); + const [density, setDensity] = useState('default'); + const [columns, setColumns] = useState(defaultColumns); + const [showAdvanced, setShowAdvanced] = useState(false); + + // 搜索参数 + const [searchTerm, setSearchTerm] = useState(''); + const [runtimeStatus, setRuntimeStatus] = useState(undefined); + + // 批量操作状态 + const [selectedDevices, setSelectedDevices] = useState([]); + const [startDialogOpen, setStartDialogOpen] = useState(false); + const [networkStackCode, setNetworkStackCode] = useState(''); + const [isSubmitting, setIsSubmitting] = useState(false); + + // Toast 提示 + const { toast } = useToast(); + + // 获取设备运行时状态列表 + const fetchDeviceRuntimes = async (params: Partial = {}) => { + setLoading(true); + try { + const result = await getDeviceRuntimes({ + pageNumber: params.pageNumber || pageNumber, + pageSize: params.pageSize || pageSize, + searchTerm: params.searchTerm !== undefined ? params.searchTerm : searchTerm, + runtimeStatus: params.runtimeStatus !== undefined ? params.runtimeStatus : runtimeStatus, + }); + + if (result.isSuccess && result.data) { + setDeviceRuntimes(result.data.items); + setTotal(result.data.totalCount); + if (params.pageNumber) setPageNumber(params.pageNumber); + if (params.pageSize) setPageSize(params.pageSize); + } else { + toast({ + title: '获取设备运行时状态失败', + description: result.errorMessages?.join(', ') || '未知错误', + variant: 'destructive', + }); + } + } catch (error) { + console.error('获取设备运行时状态失败:', error); + toast({ + title: '获取设备运行时状态失败', + description: '网络错误或服务器异常', + variant: 'destructive', + }); + } finally { + setLoading(false); + } + }; + + // 启动设备 + const handleStartDevices = async () => { + if (!networkStackCode.trim()) { + toast({ + title: '网络栈配置不能为空', + description: '请选择网络栈配置', + variant: 'destructive', + }); + return; + } + + if (selectedDevices.length === 0) { + toast({ + title: '请选择要启动的设备', + description: '请至少选择一个设备', + variant: 'destructive', + }); + return; + } + + setIsSubmitting(true); + try { + const deviceRequests: StartDeviceRequest[] = selectedDevices.map(deviceCode => ({ + deviceCode, + networkStackCode: networkStackCode.trim() + })); + + const result = await startDevices(deviceRequests); + + if (result.isSuccess && result.data) { + const { summary } = result.data; + toast({ + title: '设备启动完成', + description: `总数: ${summary?.totalCount}, 成功: ${summary?.successCount}, 失败: ${summary?.failureCount}`, + variant: summary?.failureCount === 0 ? 'default' : 'destructive', + }); + + // 刷新列表 + fetchDeviceRuntimes(); + setStartDialogOpen(false); + setSelectedDevices([]); + setNetworkStackCode(''); + } else { + toast({ + title: '设备启动失败', + description: result.errorMessages?.join(', ') || '未知错误', + variant: 'destructive', + }); + } + } catch (error) { + console.error('设备启动失败:', error); + toast({ + title: '设备启动失败', + description: '网络错误或服务器异常', + variant: 'destructive', + }); + } finally { + setIsSubmitting(false); + } + }; + + // 停止设备 + const handleStopDevice = async (deviceCode: string) => { + try { + const result = await stopDevice(deviceCode); + + if (result.isSuccess) { + toast({ + title: '设备停止成功', + description: `设备 ${deviceCode} 已停止`, + }); + // 刷新列表 + fetchDeviceRuntimes(); + } else { + toast({ + title: '设备停止失败', + description: result.errorMessages?.join(', ') || '未知错误', + variant: 'destructive', + }); + } + } catch (error) { + console.error('设备停止失败:', error); + toast({ + title: '设备停止失败', + description: '网络错误或服务器异常', + variant: 'destructive', + }); + } + }; + + // 处理设备选择 + const handleDeviceSelect = (deviceCode: string, checked: boolean) => { + if (checked) { + setSelectedDevices(prev => [...prev, deviceCode]); + } else { + setSelectedDevices(prev => prev.filter(code => code !== deviceCode)); + } + }; + + // 处理全选 + const handleSelectAll = (checked: boolean) => { + if (checked) { + setSelectedDevices(deviceRuntimes.map(device => device.deviceCode)); + } else { + setSelectedDevices([]); + } + }; + + // 查询 + const handleQuery = () => { + fetchDeviceRuntimes({ pageNumber: 1 }); + }; + + // 重置 + const handleReset = () => { + setSearchTerm(''); + setRuntimeStatus(undefined); + setSelectedDevices([]); + fetchDeviceRuntimes({ + pageNumber: 1, + searchTerm: '', + runtimeStatus: undefined + }); + }; + + // 页码变化 + const handlePageChange = (page: number) => { + fetchDeviceRuntimes({ pageNumber: page }); + }; + + // 每页数量变化 + const handlePageSizeChange = (size: number) => { + fetchDeviceRuntimes({ pageNumber: 1, pageSize: size }); + }; + + // 初始化加载 + useEffect(() => { + fetchDeviceRuntimes(); + }, []); + + // 统计信息 + const runningCount = deviceRuntimes.filter(d => d.runtimeStatus === 1).length; + const stoppedCount = deviceRuntimes.filter(d => d.runtimeStatus === 2).length; + const errorCount = deviceRuntimes.filter(d => d.runtimeStatus === 3).length; + + return ( +
+ {/* 页面标题 */} +
+
+

设备运行时管理

+

管理设备的运行时状态,包括启动、停止和状态监控

+
+
+ + {/* 统计卡片 */} +
+ + + 总设备数 + + +
{total}
+
+
+ + + 运行中 + + +
{runningCount}
+
+
+ + + 已停止 + + +
{stoppedCount}
+
+
+ + + 错误状态 + + +
{errorCount}
+
+
+
+ + {/* 搜索工具栏 */} + + + 搜索条件 + + +
+ {/* 第一行搜索字段 */} +
+
+ + setSearchTerm(e.target.value)} + /> +
+
+ + +
+
+ + {/* 高级搜索字段 */} + {showAdvanced && ( +
+
+ + +
+
+ )} + + {/* 操作按钮 */} +
+
+ + + +
+ + {/* 批量操作按钮 */} +
+ + + + + + + 批量启动设备 + +
+
+ + setNetworkStackCode(e.target.value)} + /> +
+
+ +
+ {selectedDevices.map(deviceCode => ( + + {deviceCode} + + ))} +
+
+
+ + +
+
+
+
+
+
+
+
+
+ + {/* 表格工具栏 */} + + + {/* 设备运行时表格 */} + + + {/* 分页 */} + +
+ ); +} \ No newline at end of file diff --git a/src/X1.WebUI/src/pages/instruments/DeviceForm.tsx b/src/X1.WebUI/src/pages/instruments/DeviceForm.tsx index 1f1ad35..b6561ca 100644 --- a/src/X1.WebUI/src/pages/instruments/DeviceForm.tsx +++ b/src/X1.WebUI/src/pages/instruments/DeviceForm.tsx @@ -18,8 +18,7 @@ export default function DeviceForm({ onSubmit, initialData, isEdit = false, isSu description: initialData?.description || '', ipAddress: initialData?.ipAddress || '', agentPort: initialData?.agentPort || 8080, - isEnabled: true, - isRunning: false + isEnabled: true }); const handleSubmit = (e: React.FormEvent) => { @@ -32,8 +31,7 @@ export default function DeviceForm({ onSubmit, initialData, isEdit = false, isSu deviceId: initialData?.deviceId || '', deviceName: formData.deviceName, description: formData.description, - isEnabled: formData.isEnabled, - isRunning: formData.isRunning + isEnabled: formData.isEnabled }; onSubmit(updateData); } else { diff --git a/src/X1.WebUI/src/pages/instruments/DevicesTable.tsx b/src/X1.WebUI/src/pages/instruments/DevicesTable.tsx index 3a38467..c478d81 100644 --- a/src/X1.WebUI/src/pages/instruments/DevicesTable.tsx +++ b/src/X1.WebUI/src/pages/instruments/DevicesTable.tsx @@ -16,6 +16,7 @@ import { DensityType } from '@/components/ui/TableToolbar'; * - description: 设备描述 * - agentPort: Agent端口 * - isEnabled: 是否启用 + * - runtimeStatus: 运行时状态(-1:未知, 0:初始化, 1:运行中, 2:停止中) * - createdAt: 创建时间 * * 注意:IpAddress字段在创建/更新时使用,但查询响应中不返回 @@ -44,6 +45,26 @@ function DeviceStatusBadge({ isEnabled }: { isEnabled: boolean }) { ); } +// 运行时状态徽章组件 +function RuntimeStatusBadge({ status }: { status: number }) { + const getStatusInfo = (status: number) => { + switch (status) { + case 0: return { text: '初始化', variant: 'secondary' as const }; + case 1: return { text: '运行中', variant: 'default' as const }; + case 2: return { text: '停止中', variant: 'outline' as const }; + case -1: return { text: '未知', variant: 'outline' as const }; + default: return { text: '未知', variant: 'outline' as const }; + } + }; + + const { text, variant } = getStatusInfo(status); + return ( + + {text} + + ); +} + // 日期显示组件 function DateDisplay({ date }: { date: string }) { return ( @@ -100,6 +121,8 @@ export default function DevicesTable({ return {device.agentPort}; case 'isEnabled': return ; + case 'runtimeStatus': + return ; case 'createdAt': return ; case 'actions': diff --git a/src/X1.WebUI/src/pages/instruments/DevicesView.tsx b/src/X1.WebUI/src/pages/instruments/DevicesView.tsx index a0485d3..d729968 100644 --- a/src/X1.WebUI/src/pages/instruments/DevicesView.tsx +++ b/src/X1.WebUI/src/pages/instruments/DevicesView.tsx @@ -16,7 +16,8 @@ const defaultColumns = [ { key: 'deviceCode', title: '设备编码', visible: true }, { key: 'description', title: '描述', visible: true }, { key: 'agentPort', title: 'Agent端口', visible: true }, - { key: 'isEnabled', title: '状态', visible: true }, + { key: 'isEnabled', title: '启用状态', visible: true }, + { key: 'runtimeStatus', title: '运行时状态', visible: true }, { key: 'createdAt', title: '创建时间', visible: true }, { key: 'actions', title: '操作', visible: true }, ]; @@ -56,6 +57,7 @@ const advancedFields: SearchField[] = [ * - description: 设备描述 * - agentPort: Agent端口 * - isEnabled: 是否启用 + * - runtimeStatus: 运行时状态(-1:未知, 0:初始化, 1:运行中, 2:停止中) * - createdAt: 创建时间 * * 注意:IpAddress字段在创建/更新时使用,但查询响应中不返回 @@ -86,6 +88,27 @@ export default function DevicesView() { // Toast 提示 const { toast } = useToast(); + // 运行时状态映射 + const getRuntimeStatusText = (status: number): string => { + switch (status) { + case 0: return '初始化'; + case 1: return '运行中'; + case 2: return '停止中'; + case -1: return '未知'; + default: return '未知'; + } + }; + + const getRuntimeStatusColor = (status: number): string => { + switch (status) { + case 0: return 'text-yellow-500'; // 初始化 - 黄色 + case 1: return 'text-green-500'; // 运行中 - 绿色 + case 2: return 'text-orange-500'; // 停止中 - 橙色 + case -1: return 'text-gray-400'; // 未知 - 灰色 + default: return 'text-gray-400'; + } + }; + const fetchDevices = async (params: Partial = {}) => { setLoading(true); const queryParams: GetDevicesRequest = { diff --git a/src/X1.WebUI/src/routes/AppRouter.tsx b/src/X1.WebUI/src/routes/AppRouter.tsx index 92e8683..177fa3e 100644 --- a/src/X1.WebUI/src/routes/AppRouter.tsx +++ b/src/X1.WebUI/src/routes/AppRouter.tsx @@ -31,6 +31,9 @@ const UEAnalysisView = lazy(() => import('@/pages/analysis/UEAnalysisView')); // 设备管理页面 const DevicesView = lazy(() => import('@/pages/instruments/DevicesView')); +// 设备运行时管理页面 +const DeviceRuntimesView = lazy(() => import('@/pages/device-runtimes/DeviceRuntimesView')); +const DeviceRuntimeDetail = lazy(() => import('@/pages/device-runtimes/DeviceRuntimeDetail')); // 协议管理页面 const ProtocolsView = lazy(() => import('@/pages/protocols/ProtocolsView')); // RAN配置管理页面 @@ -208,6 +211,23 @@ export function AppRouter() { } /> + + } /> + + + + + + } /> + + + + + + } /> + {/* 网络栈配置管理路由 */} diff --git a/src/X1.WebUI/src/services/deviceRuntimeService.ts b/src/X1.WebUI/src/services/deviceRuntimeService.ts new file mode 100644 index 0000000..f9591e2 --- /dev/null +++ b/src/X1.WebUI/src/services/deviceRuntimeService.ts @@ -0,0 +1,178 @@ +import { httpClient } from '@/lib/http-client'; +import { OperationResult } from '@/types/auth'; +import { API_PATHS } from '@/constants/api'; + +// 设备运行时状态枚举 +export enum DeviceRuntimeStatus { + Running = 'Running', + Stopped = 'Stopped', + Error = 'Error', + Unknown = 'Unknown' +} + +// 设备运行时信息接口 - 对应 DeviceRuntimeDto +export interface DeviceRuntime { + deviceCode: string; + runtimeStatus: number; // 对应后端的 int 类型 + networkStackCode?: string; + name?: string; // 对应后端的 Name 字段 + createdAt: string; // 对应后端的 DateTime +} + +// 获取设备运行时列表请求接口 - 对应 GetDeviceRuntimesQuery +export interface GetDeviceRuntimesRequest { + pageNumber?: number; + pageSize?: number; + searchTerm?: string; + runtimeStatus?: number; // 对应后端的 int 类型 +} + +// 获取设备运行时列表响应接口 - 对应 GetDeviceRuntimesResponse +export interface GetDeviceRuntimesResponse { + totalCount: number; + pageNumber: number; + pageSize: number; + totalPages: number; + items: DeviceRuntime[]; // 对应后端的 Items 字段 +} + +// 获取设备运行时状态响应接口 - 对应 GetDeviceRuntimeStatusResponse +export interface GetDeviceRuntimeStatusResponse { + deviceCode: string; + runtimeStatus: string; // 对应后端的 string 类型 + runtimeCode?: string; + networkStackCode?: string; + updatedAt?: string; // 对应后端的 DateTime +} + +// 启动设备请求接口 - 对应 DeviceStartRequest +export interface StartDeviceRequest { + deviceCode: string; + networkStackCode: string; // 必填字段 +} + +// 启动设备响应接口 - 对应 StartDeviceRuntimeResponse +export interface StartDeviceRuntimeResponse { + id?: string; + deviceCode?: string; + runtimeStatus?: string; + networkStackCode?: string; + updatedAt: string; // 对应后端的 DateTime + deviceResults?: DeviceStartResult[]; // 对应后端的 DeviceResults + summary?: BatchOperationSummary; // 对应后端的 Summary +} + +// 设备启动结果 - 对应 DeviceStartResult +export interface DeviceStartResult { + deviceCode: string; + id: string; + runtimeStatus: string; + networkStackCode?: string; + updatedAt: string; // 对应后端的 DateTime + isSuccess: boolean; + errorMessage?: string; +} + +// 批量操作统计 - 对应 BatchOperationSummary +export interface BatchOperationSummary { + totalCount: number; + successCount: number; + failureCount: number; + successRate: number; // 对应后端的计算属性 +} + +// 停止设备响应接口 - 对应 StopDeviceRuntimeResponse +export interface StopDeviceRuntimeResponse { + id: string; + deviceCode: string; + runtimeStatus: string; + runtimeCode?: string; + networkStackCode?: string; + updatedAt: string; // 对应后端的 DateTime +} + +class DeviceRuntimeService { + private readonly baseUrl = API_PATHS.DEVICE_RUNTIMES; + + // 获取设备运行时状态列表 + async getDeviceRuntimes(params: GetDeviceRuntimesRequest = {}): Promise> { + 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.runtimeStatus !== undefined) queryParams.append('runtimeStatus', params.runtimeStatus.toString()); + + const url = `${this.baseUrl}?${queryParams.toString()}`; + return httpClient.get(url); + } + + // 根据设备编号获取设备运行时状态 + async getDeviceRuntimeStatus(deviceCode: string): Promise> { + return httpClient.get(`${this.baseUrl}/${deviceCode}`); + } + + // 批量启动设备 + async startDevices(deviceRequests: StartDeviceRequest[]): Promise> { + return httpClient.post(`${this.baseUrl}/start`, { + deviceRequests + }); + } + + // 停止单个设备 + async stopDevice(deviceCode: string): Promise> { + return httpClient.post(`${this.baseUrl}/${deviceCode}/stop`); + } + + // 获取设备运行时状态的可读描述 + getRuntimeStatusDescription(status: number | string): string { + const statusStr = typeof status === 'number' ? this.getRuntimeStatusString(status) : status; + switch (statusStr) { + case 'Running': + return '运行中'; + case 'Stopped': + return '已停止'; + case 'Error': + return '错误'; + case 'Unknown': + return '未知'; + default: + return '未知状态'; + } + } + + // 获取设备运行时状态的颜色 + getRuntimeStatusColor(status: number | string): string { + const statusStr = typeof status === 'number' ? this.getRuntimeStatusString(status) : status; + switch (statusStr) { + case 'Running': + return 'success'; + case 'Stopped': + return 'warning'; + case 'Error': + return 'error'; + case 'Unknown': + return 'default'; + default: + return 'default'; + } + } + + // 将数字状态转换为字符串状态 + private getRuntimeStatusString(status: number): string { + switch (status) { + case 0: + return 'Initializing'; + case 1: + return 'Running'; + case 2: + return 'Stopped'; + case 3: + return 'Error'; + default: + return 'Unknown'; + } + } +} + +export const deviceRuntimeService = new DeviceRuntimeService(); \ No newline at end of file diff --git a/src/X1.WebUI/src/services/deviceService.ts b/src/X1.WebUI/src/services/deviceService.ts new file mode 100644 index 0000000..791d90d --- /dev/null +++ b/src/X1.WebUI/src/services/deviceService.ts @@ -0,0 +1,140 @@ +import { httpClient } from '@/lib/http-client'; +import { OperationResult } from '@/types/auth'; +import { API_PATHS } from '@/constants/api'; + +// 设备信息接口 +export interface Device { + deviceId: string; + deviceName: string; + deviceCode: string; + deviceType: string; + description?: string; + isActive: boolean; + createdAt: string; + updatedAt: string; +} + +// 获取设备列表请求接口 +export interface GetDevicesRequest { + pageNumber?: number; + pageSize?: number; + searchTerm?: string; + deviceType?: string; + isActive?: boolean; +} + +// 获取设备列表响应接口 +export interface GetDevicesResponse { + totalCount: number; + pageNumber: number; + pageSize: number; + totalPages: number; + hasPreviousPage: boolean; + hasNextPage: boolean; + items: Device[]; +} + +// 获取设备详情响应接口 +export interface GetDeviceByIdResponse { + deviceId: string; + deviceName: string; + deviceCode: string; + deviceType: string; + description?: string; + isActive: boolean; + createdAt: string; + updatedAt: string; +} + +// 创建设备请求接口 +export interface CreateDeviceRequest { + deviceName: string; + deviceCode: string; + deviceType: string; + description?: string; + isActive?: boolean; +} + +// 创建设备响应接口 +export interface CreateDeviceResponse { + deviceId: string; + deviceName: string; + deviceCode: string; + deviceType: string; + description?: string; + isActive: boolean; + createdAt: string; + updatedAt: string; +} + +// 更新设备请求接口 +export interface UpdateDeviceRequest { + deviceId: string; + deviceName: string; + deviceCode: string; + deviceType: string; + description?: string; + isActive?: boolean; +} + +// 更新设备响应接口 +export interface UpdateDeviceResponse { + deviceId: string; + deviceName: string; + deviceCode: string; + deviceType: string; + description?: string; + isActive: boolean; + createdAt: string; + updatedAt: string; +} + +class DeviceService { + private readonly baseUrl = API_PATHS.DEVICES; + + // 获取设备列表 + async getDevices(params: GetDevicesRequest = {}): Promise> { + 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.deviceType) queryParams.append('deviceType', params.deviceType); + if (params.isActive !== undefined) queryParams.append('isActive', params.isActive.toString()); + + const url = `${this.baseUrl}?${queryParams.toString()}`; + return httpClient.get(url); + } + + // 根据ID获取设备详情 + async getDeviceById(deviceId: string): Promise> { + return httpClient.get(`${this.baseUrl}/${deviceId}`); + } + + // 创建设备 + async createDevice(data: CreateDeviceRequest): Promise> { + return httpClient.post(this.baseUrl, data); + } + + // 更新设备 + async updateDevice(deviceId: string, data: UpdateDeviceRequest): Promise> { + return httpClient.put(`${this.baseUrl}/${deviceId}`, data); + } + + // 删除设备 + async deleteDevice(deviceId: string): Promise> { + return httpClient.delete(`${this.baseUrl}/${deviceId}`); + } + + // 获取设备状态的可读描述 + getDeviceStatusDescription(isActive: boolean): string { + return isActive ? '启用' : '禁用'; + } + + // 获取设备状态的颜色 + getDeviceStatusColor(isActive: boolean): string { + return isActive ? 'success' : 'error'; + } +} + +export const deviceService = new DeviceService(); \ No newline at end of file diff --git a/src/X1.WebUI/src/services/instrumentService.ts b/src/X1.WebUI/src/services/instrumentService.ts index 5221ba5..7360dd0 100644 --- a/src/X1.WebUI/src/services/instrumentService.ts +++ b/src/X1.WebUI/src/services/instrumentService.ts @@ -10,8 +10,8 @@ export interface Device { description: string; agentPort: number; isEnabled: boolean; - isRunning: boolean; createdAt: string; + runtimeStatus: number; // 运行时状态(-1表示未知状态) } // 获取设备列表请求接口 - 匹配后端GetDevicesQuery @@ -40,7 +40,6 @@ export interface CreateDeviceRequest { ipAddress: string; agentPort: number; isEnabled?: boolean; - isRunning?: boolean; } // 创建设备响应接口 - 匹配后端CreateDeviceResponse @@ -51,7 +50,6 @@ export interface CreateDeviceResponse { description: string; agentPort: number; isEnabled: boolean; - isRunning: boolean; createdAt: string; } @@ -61,7 +59,6 @@ export interface UpdateDeviceRequest { deviceName: string; description?: string; isEnabled?: boolean; - isRunning?: boolean; } // 更新设备响应接口 - 匹配后端UpdateDeviceResponse @@ -72,7 +69,6 @@ export interface UpdateDeviceResponse { description: string; agentPort: number; isEnabled: boolean; - isRunning: boolean; updatedAt?: string; } diff --git a/src/X1.WebUI/src/services/permissionService.ts b/src/X1.WebUI/src/services/permissionService.ts new file mode 100644 index 0000000..c888175 --- /dev/null +++ b/src/X1.WebUI/src/services/permissionService.ts @@ -0,0 +1,53 @@ +import { httpClient } from '@/lib/http-client'; +import { OperationResult } from '@/types/auth'; +import { API_PATHS } from '@/constants/api'; + +// 权限信息接口 +export interface Permission { + id: string; + name: string; + description?: string; + createdAt: string; + updatedAt: string; +} + +// 创建权限请求接口 +export interface CreatePermissionRequest { + name: string; + description?: string; +} + +// 创建权限响应接口 +export interface CreatePermissionResponse { + id: string; + name: string; + description?: string; + createdAt: string; + updatedAt: string; +} + +class PermissionService { + private readonly baseUrl = API_PATHS.PERMISSIONS; + + // 创建权限 + async createPermission(data: CreatePermissionRequest): Promise> { + return httpClient.post(this.baseUrl, data); + } + + // 获取权限列表(如果需要的话) + async getPermissions(): Promise> { + return httpClient.get(this.baseUrl); + } + + // 根据ID获取权限详情(如果需要的话) + async getPermissionById(permissionId: string): Promise> { + return httpClient.get(`${this.baseUrl}/${permissionId}`); + } + + // 删除权限(如果需要的话) + async deletePermission(permissionId: string): Promise> { + return httpClient.delete(`${this.baseUrl}/${permissionId}`); + } +} + +export const permissionService = new PermissionService(); \ No newline at end of file diff --git a/src/X1.WebUI/src/services/rolePermissionService.ts b/src/X1.WebUI/src/services/rolePermissionService.ts new file mode 100644 index 0000000..a3b8d2e --- /dev/null +++ b/src/X1.WebUI/src/services/rolePermissionService.ts @@ -0,0 +1,102 @@ +import { httpClient } from '@/lib/http-client'; +import { OperationResult } from '@/types/auth'; +import { API_PATHS } from '@/constants/api'; + +// 权限信息接口 +export interface Permission { + id: string; + name: string; + description?: string; +} + +// 角色权限信息接口 +export interface RolePermission { + roleId: string; + roleName: string; + permissions: Permission[]; +} + +// 获取角色权限请求接口 +export interface GetRolePermissionsRequest { + roleId: string; + includeDetails?: boolean; +} + +// 获取角色权限响应接口 +export interface GetRolePermissionsResponse { + roleId: string; + roleName: string; + permissions: Permission[]; +} + +// 添加角色权限请求接口 +export interface AddRolePermissionsRequest { + roleId: string; + permissionIds: string[]; +} + +// 添加角色权限响应接口 +export interface AddRolePermissionsResponse { + roleId: string; + addedPermissionIds: string[]; + failedPermissionIds: string[]; + addedCount: number; + failedCount: number; +} + +// 删除角色权限请求接口 +export interface DeleteRolePermissionsRequest { + roleId: string; + permissionIds: string[]; +} + +// 删除角色权限响应接口 +export interface DeleteRolePermissionsResponse { + roleId: string; + deletedCount: number; + failedPermissionIds: string[]; + failedCount: number; +} + +class RolePermissionService { + private readonly baseUrl = API_PATHS.ROLE_PERMISSIONS; + + // 获取角色权限 + async getRolePermissions(roleId: string, includeDetails: boolean = true): Promise> { + const url = `${this.baseUrl}/${roleId}?includeDetails=${includeDetails}`; + return httpClient.get(url); + } + + // 添加角色权限 + async addRolePermissions(data: AddRolePermissionsRequest): Promise> { + return httpClient.post(this.baseUrl, data); + } + + // 删除角色权限 + async deleteRolePermissions(data: DeleteRolePermissionsRequest): Promise> { + return httpClient.delete(this.baseUrl, { data }); + } + + // 批量添加权限到角色 + async batchAddPermissions(roleId: string, permissionIds: string[]): Promise> { + return this.addRolePermissions({ roleId, permissionIds }); + } + + // 批量从角色删除权限 + async batchRemovePermissions(roleId: string, permissionIds: string[]): Promise> { + return this.deleteRolePermissions({ roleId, permissionIds }); + } + + // 获取角色权限统计信息 + getRolePermissionStats(response: GetRolePermissionsResponse): { + totalPermissions: number; + permissionNames: string[]; + } { + return { + totalPermissions: response.permissions.length, + permissionNames: response.permissions.map(p => p.name) + }; + } +} + +export const rolePermissionService = new RolePermissionService(); \ No newline at end of file diff --git a/src/modify.md b/src/modify.md index 4c6956c..1899ea4 100644 --- a/src/modify.md +++ b/src/modify.md @@ -1,5 +1,811 @@ # 修改记录 +## 2025-01-29 根据DeviceRuntimesController实现X1.WebUI.src.services + +### 修改原因 +根据后端 `DeviceRuntimesController` 的实现,为前端 `X1.WebUI/src/services` 目录创建对应的服务,实现设备运行时状态管理的完整功能。 + +### 新增文件 + +#### 1. 设备运行时服务 +- `X1.WebUI/src/services/deviceRuntimeService.ts` - 设备运行时状态管理服务 + +#### 2. 设备管理服务 +- `X1.WebUI/src/services/deviceService.ts` - 设备管理服务 + +#### 3. 权限管理服务 +- `X1.WebUI/src/services/permissionService.ts` - 权限管理服务 + +#### 4. 角色权限管理服务 +- `X1.WebUI/src/services/rolePermissionService.ts` - 角色权限管理服务 + +### 修改文件 + +#### 1. API常量配置 +- `X1.WebUI/src/constants/api.ts` - 添加设备运行时、设备、权限、角色权限相关的API路径 + +### 功能特性 + +#### 1. 设备运行时服务 (deviceRuntimeService.ts) +- **设备运行时状态枚举**:`DeviceRuntimeStatus`(Running、Stopped、Error、Unknown) +- **核心功能**: + - `getDeviceRuntimes()` - 获取设备运行时状态列表(支持分页、搜索、状态过滤) + - `getDeviceRuntimeStatus()` - 根据设备编号获取设备运行时状态 + - `startDevices()` - 批量启动设备 + - `stopDevice()` - 停止单个设备 +- **辅助功能**: + - `getRuntimeStatusDescription()` - 获取状态的可读描述 + - `getRuntimeStatusColor()` - 获取状态对应的颜色 + +#### 2. 设备管理服务 (deviceService.ts) +- **核心功能**: + - `getDevices()` - 获取设备列表(支持分页、搜索、类型过滤、状态过滤) + - `getDeviceById()` - 根据ID获取设备详情 + - `createDevice()` - 创建设备 + - `updateDevice()` - 更新设备 + - `deleteDevice()` - 删除设备 +- **辅助功能**: + - `getDeviceStatusDescription()` - 获取设备状态描述 + - `getDeviceStatusColor()` - 获取设备状态颜色 + +#### 3. 权限管理服务 (permissionService.ts) +- **核心功能**: + - `createPermission()` - 创建权限 + - `getPermissions()` - 获取权限列表 + - `getPermissionById()` - 根据ID获取权限详情 + - `deletePermission()` - 删除权限 + +#### 4. 角色权限管理服务 (rolePermissionService.ts) +- **核心功能**: + - `getRolePermissions()` - 获取角色权限 + - `addRolePermissions()` - 添加角色权限 + - `deleteRolePermissions()` - 删除角色权限 + - `batchAddPermissions()` - 批量添加权限到角色 + - `batchRemovePermissions()` - 批量从角色删除权限 +- **辅助功能**: + - `getRolePermissionStats()` - 获取角色权限统计信息 + +### 技术特性 + +#### 1. 类型安全 +- 完整的 TypeScript 接口定义 +- 与后端 API 响应格式完全匹配 +- 提供良好的开发体验和代码提示 + +#### 2. 错误处理 +- 统一的错误处理机制 +- 使用 `OperationResult` 包装响应 +- 详细的错误信息记录 + +#### 3. HTTP 客户端集成 +- 使用 `httpClient` 进行 API 调用 +- 支持 GET、POST、PUT、DELETE 方法 +- 自动处理查询参数构建 + +#### 4. 查询参数支持 +- 分页参数:`pageNumber`、`pageSize` +- 搜索参数:`searchTerm` +- 过滤参数:状态、类型等 +- 排序参数:按时间、名称等排序 + +### API 路径配置 + +#### 1. 新增的 API 路径 +```typescript +export const API_PATHS = { + // 设备相关 + DEVICES: '/devices', + DEVICE_RUNTIMES: '/api/device-runtimes', + + // 权限相关 + PERMISSIONS: '/api/permissions', + ROLE_PERMISSIONS: '/api/role-permissions', + + // ... 其他现有路径 +} +``` + +### 使用示例 + +#### 1. 设备运行时管理 +```typescript +// 获取设备运行时状态列表 +const result = await deviceRuntimeService.getDeviceRuntimes({ + pageNumber: 1, + pageSize: 10, + searchTerm: 'DEV001', + runtimeStatus: 1 // 对应 Running 状态 +}); + +// 启动设备 +const startResult = await deviceRuntimeService.startDevices([ + { deviceCode: 'DEV001', networkStackCode: 'STACK001' }, + { deviceCode: 'DEV002', networkStackCode: 'STACK002' } +]); + +// 停止设备 +const stopResult = await deviceRuntimeService.stopDevice('DEV001'); +``` + +#### 2. 设备管理 +```typescript +// 获取设备列表 +const result = await deviceService.getDevices({ + pageNumber: 1, + pageSize: 10, + searchTerm: 'LTE', + deviceType: 'Cellular', + isActive: true +}); + +// 创建设备 +const createResult = await deviceService.createDevice({ + deviceName: '新设备', + deviceCode: 'DEV003', + deviceType: 'Cellular', + description: '测试设备' +}); +``` + +#### 3. 权限管理 +```typescript +// 创建权限 +const result = await permissionService.createPermission({ + name: 'CreateUser', + description: '创建用户的权限' +}); +``` + +#### 4. 角色权限管理 +```typescript +// 获取角色权限 +const result = await rolePermissionService.getRolePermissions('roleId', true); + +// 添加角色权限 +const addResult = await rolePermissionService.addRolePermissions({ + roleId: 'roleId', + permissionIds: ['perm1', 'perm2'] +}); +``` + +### 影响范围 +- **前端服务**:提供了完整的设备运行时、设备、权限、角色权限管理前端服务 +- **API 集成**:与后端控制器完全对应 +- **类型安全**:提供了完整的 TypeScript 类型定义 +- **开发体验**:统一的服务接口,便于前端开发使用 + +### 后续工作建议 +1. **前端页面开发**:基于这些服务开发对应的前端页面 +2. **组件开发**:开发设备运行时状态显示组件 +3. **状态管理**:集成到前端状态管理系统中 +4. **错误处理**:完善错误处理和用户提示 +5. **测试验证**:为所有服务方法添加单元测试 + +## 2025-01-29 修复设备运行时服务接口与后端控制器完全对应 + +### 修改原因 +确保前端设备运行时服务的接口定义与后端控制器完全对应,避免数据类型不匹配和字段缺失问题。 + +### 修改文件 +- `X1.WebUI/src/services/deviceRuntimeService.ts` - 更新接口定义以匹配后端响应实体 + +### 修改内容 + +#### 1. 设备运行时信息接口 (DeviceRuntime) +**对应后端 DeviceRuntimeDto**: +- `deviceCode: string` - 设备编号 +- `runtimeStatus: number` - 运行时状态(对应后端的 int 类型) +- `networkStackCode?: string` - 网络栈配置编号 +- `name?: string` - 设备名称(对应后端的 Name 字段) +- `createdAt: string` - 创建时间(对应后端的 DateTime) + +#### 2. 获取设备运行时状态响应接口 (GetDeviceRuntimeStatusResponse) +**对应后端 GetDeviceRuntimeStatusResponse**: +- `deviceCode: string` - 设备编号 +- `runtimeStatus: string` - 运行时状态(对应后端的 string 类型) +- `runtimeCode?: string` - 运行编码 +- `networkStackCode?: string` - 网络栈配置编号 + +#### 3. 启动设备请求接口 (StartDeviceRequest) +**对应后端 DeviceStartRequest**: +- `deviceCode: string` - 设备编号 +- `networkStackCode: string` - 网络栈配置编号(必填字段) + +#### 4. 启动设备响应接口 (StartDeviceRuntimeResponse) +**对应后端 StartDeviceRuntimeResponse**: +- `id?: string` - 运行时状态ID +- `deviceCode?: string` - 设备编号 +- `runtimeStatus?: string` - 运行时状态 +- `networkStackCode?: string` - 网络栈配置编号 +- `updatedAt: string` - 更新时间 +- `deviceResults?: DeviceStartResult[]` - 设备启动结果列表 +- `summary?: BatchOperationSummary` - 批量操作统计 + +#### 5. 设备启动结果接口 (DeviceStartResult) +**对应后端 DeviceStartResult**: +- `deviceCode: string` - 设备编号 +- `id: string` - 运行时状态ID +- `runtimeStatus: string` - 运行时状态 +- `networkStackCode?: string` - 网络栈配置编号 +- `updatedAt: string` - 更新时间 +- `isSuccess: boolean` - 是否成功 +- `errorMessage?: string` - 错误信息 + +#### 6. 批量操作统计接口 (BatchOperationSummary) +**对应后端 BatchOperationSummary**: +- `totalCount: number` - 总设备数 +- `successCount: number` - 成功数量 +- `failureCount: number` - 失败数量 +- `successRate: number` - 成功率(对应后端的计算属性) + +#### 7. 停止设备响应接口 (StopDeviceRuntimeResponse) +**对应后端 StopDeviceRuntimeResponse**: +- `id: string` - 运行时状态ID +- `deviceCode: string` - 设备编号 +- `runtimeStatus: string` - 运行时状态 +- `runtimeCode?: string` - 运行编码 +- `networkStackCode?: string` - 网络栈配置编号 +- `updatedAt: string` - 更新时间 + +### 技术改进 + +#### 1. 状态类型处理 +- **数字状态转换**:添加 `getRuntimeStatusString()` 方法,将数字状态转换为字符串状态 +- **状态描述方法**:更新 `getRuntimeStatusDescription()` 和 `getRuntimeStatusColor()` 方法,支持数字和字符串状态 +- **状态映射**: + - 0: Initializing(初始化) + - 1: Running(运行中) + - 2: Stopped(已停止) + - 3: Error(错误) + +#### 2. 查询参数处理 +- **运行时状态过滤**:修复 `runtimeStatus` 参数处理,支持数字类型 +- **参数验证**:使用 `!== undefined` 检查,避免 0 值被忽略 + +#### 3. 接口注释 +- **对应关系说明**:为每个接口添加注释,说明对应的后端实体 +- **字段类型说明**:标注字段类型与后端的对应关系 + +### 影响范围 +- **类型安全**:确保前端接口定义与后端API完全匹配 +- **数据一致性**:避免因字段不匹配导致的数据丢失 +- **开发体验**:提供准确的类型提示和代码补全 +- **错误减少**:减少运行时类型错误 + +### 使用示例更新 +```typescript +// 获取设备运行时状态列表(使用数字状态过滤) +const result = await deviceRuntimeService.getDeviceRuntimes({ + pageNumber: 1, + pageSize: 10, + searchTerm: 'DEV001', + runtimeStatus: 1 // 对应 Running 状态 +}); + +// 启动设备(需要提供网络栈配置编号) +const startResult = await deviceRuntimeService.startDevices([ + { deviceCode: 'DEV001', networkStackCode: 'STACK001' }, + { deviceCode: 'DEV002', networkStackCode: 'STACK002' } +]); + +// 处理启动结果 +if (startResult.isSuccess && startResult.data?.summary) { + console.log(`启动完成:总数 ${startResult.data.summary.totalCount},成功 ${startResult.data.summary.successCount},失败 ${startResult.data.summary.failureCount}`); +} +``` + +## 2025-01-29 实现设备运行时管理前端界面 + +### 修改原因 +为设备运行时管理功能实现完整的前端界面,包括列表页面、详情页面、搜索功能、批量操作等,提供用户友好的操作体验。 + +### 新增文件 + +#### 1. 设备运行时管理页面 +- `X1.WebUI/src/pages/device-runtimes/DeviceRuntimesView.tsx` - 设备运行时管理主页面 +- `X1.WebUI/src/pages/device-runtimes/DeviceRuntimesTable.tsx` - 设备运行时表格组件 +- `X1.WebUI/src/pages/device-runtimes/DeviceRuntimeDetail.tsx` - 设备运行时详情页面 + +### 修改文件 + +#### 1. 路由配置 +- `X1.WebUI/src/routes/AppRouter.tsx` - 添加设备运行时管理路由 + +#### 2. 菜单配置 +- `X1.WebUI/src/constants/menuConfig.ts` - 添加设备运行时管理菜单项和权限 + +#### 3. 服务接口 +- `X1.WebUI/src/services/deviceRuntimeService.ts` - 修复GetDeviceRuntimeStatusResponse接口,添加updatedAt字段 + +### 功能特性 + +#### 1. 设备运行时管理主页面 (DeviceRuntimesView.tsx) +- **统计卡片**:显示总设备数、运行中、已停止、错误状态的数量 +- **搜索功能**:支持按设备编号/名称搜索、按运行时状态过滤 +- **批量操作**:支持批量启动设备,需要指定网络栈配置编号 +- **表格显示**:显示设备运行时状态列表,支持分页、排序、列显示控制 +- **实时操作**:支持单个设备停止操作 + +#### 2. 设备运行时表格组件 (DeviceRuntimesTable.tsx) +- **选择功能**:支持单选、全选设备 +- **状态显示**:使用不同颜色的Badge显示运行时状态 +- **操作菜单**:根据设备状态显示不同的操作选项 +- **响应式设计**:支持紧凑、默认、舒适三种密度模式 +- **加载状态**:显示加载中和空数据状态 + +#### 3. 设备运行时详情页面 (DeviceRuntimeDetail.tsx) +- **状态概览**:显示设备运行时状态和基本信息 +- **详细信息**:分卡片显示网络配置和时间信息 +- **实时刷新**:支持手动刷新设备状态 +- **状态持续时间**:计算并显示当前状态的持续时间 +- **导航功能**:提供返回列表的导航 + +### 技术特性 + +#### 1. 组件设计 +- **模块化**:将功能拆分为独立的组件,便于维护和复用 +- **类型安全**:完整的TypeScript类型定义 +- **响应式**:支持不同屏幕尺寸的响应式布局 +- **可访问性**:遵循ARIA标准,支持键盘导航 + +#### 2. 状态管理 +- **本地状态**:使用React hooks管理组件状态 +- **加载状态**:统一的加载和错误状态处理 +- **表单状态**:搜索条件和批量操作的表单状态管理 + +#### 3. 用户体验 +- **即时反馈**:操作后立即显示结果和状态变化 +- **错误处理**:友好的错误提示和恢复机制 +- **操作确认**:重要操作提供确认对话框 +- **进度指示**:长时间操作显示进度状态 + +### 界面功能 + +#### 1. 搜索和过滤 +```typescript +// 搜索条件 +const searchConditions = { + searchTerm: string, // 设备编号或名称 + runtimeStatus: number, // 运行时状态过滤 + pageSize: number // 每页显示数量 +}; +``` + +#### 2. 批量操作 +```typescript +// 批量启动设备 +const batchStartDevices = async (deviceCodes: string[], networkStackCode: string) => { + const deviceRequests = deviceCodes.map(code => ({ + deviceCode: code, + networkStackCode: networkStackCode + })); + return await startDevices(deviceRequests); +}; +``` + +#### 3. 状态显示 +```typescript +// 状态映射 +const statusMapping = { + 0: { text: '初始化', color: 'default' }, + 1: { text: '运行中', color: 'success' }, + 2: { text: '已停止', color: 'warning' }, + 3: { text: '错误', color: 'error' } +}; +``` + +### 路由配置 + +#### 1. 新增路由 +```typescript +// 设备运行时管理路由 + + } /> + } /> + } /> + +``` + +#### 2. 权限控制 +```typescript +// 权限配置 +const permissions = { + 'deviceruntimes.view': '查看设备运行时', + 'deviceruntimes.manage': '管理设备运行时' +}; +``` + +### 菜单配置 + +#### 1. 新增菜单项 +```typescript +{ + title: '设备运行时', + icon: Gauge, + href: '/dashboard/device-runtimes', + permission: 'deviceruntimes.view', + children: [ + { + title: '运行时状态', + href: '/dashboard/device-runtimes/list', + permission: 'deviceruntimes.view', + } + ], +} +``` + +### 使用流程 + +#### 1. 查看设备运行时状态 +1. 导航到"设备运行时" → "运行时状态" +2. 查看设备运行时状态列表 +3. 使用搜索和过滤功能查找特定设备 +4. 点击设备编号查看详细信息 + +#### 2. 批量启动设备 +1. 在设备列表中勾选要启动的设备 +2. 点击"批量启动"按钮 +3. 在弹出的对话框中输入网络栈配置编号 +4. 确认启动操作 + +#### 3. 停止单个设备 +1. 在设备列表中找到运行中的设备 +2. 点击操作菜单中的"停止设备" +3. 确认停止操作 + +#### 4. 查看设备详情 +1. 点击设备编号进入详情页面 +2. 查看设备的运行时状态、网络配置等信息 +3. 使用刷新按钮更新状态 +4. 返回列表页面 + +### 影响范围 +- **前端界面**:提供了完整的设备运行时管理界面 +- **用户体验**:改善了设备运行时管理的操作体验 +- **功能完整性**:实现了设备运行时管理的所有核心功能 +- **系统集成**:与现有的权限系统和导航系统完全集成 + +### 后续优化建议 +1. **实时更新**:添加WebSocket支持,实现设备状态的实时更新 +2. **历史记录**:添加设备运行时历史记录查看功能 +3. **批量导入**:支持批量导入设备配置 +4. **监控告警**:添加设备状态异常告警功能 +5. **操作日志**:记录和查看设备操作历史 + +### 修改原因 +根据后端 `DeviceRuntimesController` 的实现,为前端 `X1.WebUI/src/services` 目录创建对应的服务,实现设备运行时状态管理的完整功能。 + +### 新增文件 + +#### 1. 设备运行时服务 +- `X1.WebUI/src/services/deviceRuntimeService.ts` - 设备运行时状态管理服务 + +#### 2. 设备管理服务 +- `X1.WebUI/src/services/deviceService.ts` - 设备管理服务 + +#### 3. 权限管理服务 +- `X1.WebUI/src/services/permissionService.ts` - 权限管理服务 + +#### 4. 角色权限管理服务 +- `X1.WebUI/src/services/rolePermissionService.ts` - 角色权限管理服务 + +### 修改文件 + +#### 1. API常量配置 +- `X1.WebUI/src/constants/api.ts` - 添加设备运行时、设备、权限、角色权限相关的API路径 + +### 功能特性 + +#### 1. 设备运行时服务 (deviceRuntimeService.ts) +- **设备运行时状态枚举**:`DeviceRuntimeStatus`(Running、Stopped、Error、Unknown) +- **核心功能**: + - `getDeviceRuntimes()` - 获取设备运行时状态列表(支持分页、搜索、状态过滤) + - `getDeviceRuntimeStatus()` - 根据设备编号获取设备运行时状态 + - `startDevices()` - 批量启动设备 + - `stopDevice()` - 停止单个设备 +- **辅助功能**: + - `getRuntimeStatusDescription()` - 获取状态的可读描述 + - `getRuntimeStatusColor()` - 获取状态对应的颜色 + +#### 2. 设备管理服务 (deviceService.ts) +- **核心功能**: + - `getDevices()` - 获取设备列表(支持分页、搜索、类型过滤、状态过滤) + - `getDeviceById()` - 根据ID获取设备详情 + - `createDevice()` - 创建设备 + - `updateDevice()` - 更新设备 + - `deleteDevice()` - 删除设备 +- **辅助功能**: + - `getDeviceStatusDescription()` - 获取设备状态描述 + - `getDeviceStatusColor()` - 获取设备状态颜色 + +#### 3. 权限管理服务 (permissionService.ts) +- **核心功能**: + - `createPermission()` - 创建权限 + - `getPermissions()` - 获取权限列表 + - `getPermissionById()` - 根据ID获取权限详情 + - `deletePermission()` - 删除权限 + +#### 4. 角色权限管理服务 (rolePermissionService.ts) +- **核心功能**: + - `getRolePermissions()` - 获取角色权限 + - `addRolePermissions()` - 添加角色权限 + - `deleteRolePermissions()` - 删除角色权限 + - `batchAddPermissions()` - 批量添加权限到角色 + - `batchRemovePermissions()` - 批量从角色删除权限 +- **辅助功能**: + - `getRolePermissionStats()` - 获取角色权限统计信息 + +### 技术特性 + +#### 1. 类型安全 +- 完整的 TypeScript 接口定义 +- 与后端 API 响应格式完全匹配 +- 提供良好的开发体验和代码提示 + +#### 2. 错误处理 +- 统一的错误处理机制 +- 使用 `OperationResult` 包装响应 +- 详细的错误信息记录 + +#### 3. HTTP 客户端集成 +- 使用 `httpClient` 进行 API 调用 +- 支持 GET、POST、PUT、DELETE 方法 +- 自动处理查询参数构建 + +#### 4. 查询参数支持 +- 分页参数:`pageNumber`、`pageSize` +- 搜索参数:`searchTerm` +- 过滤参数:状态、类型等 +- 排序参数:按时间、名称等排序 + +### API 路径配置 + +#### 1. 新增的 API 路径 +```typescript +export const API_PATHS = { + // 设备相关 + DEVICES: '/devices', + DEVICE_RUNTIMES: '/api/device-runtimes', + + // 权限相关 + PERMISSIONS: '/api/permissions', + ROLE_PERMISSIONS: '/api/role-permissions', + + // ... 其他现有路径 +} +``` + +### 使用示例 + +#### 1. 设备运行时管理 +```typescript +// 获取设备运行时状态列表 +const result = await deviceRuntimeService.getDeviceRuntimes({ + pageNumber: 1, + pageSize: 10, + searchTerm: 'DEV001', + runtimeStatus: DeviceRuntimeStatus.Running +}); + +// 启动设备 +const startResult = await deviceRuntimeService.startDevices([ + { deviceCode: 'DEV001', deviceName: '设备1' }, + { deviceCode: 'DEV002', deviceName: '设备2' } +]); + +// 停止设备 +const stopResult = await deviceRuntimeService.stopDevice('DEV001'); +``` + +#### 2. 设备管理 +```typescript +// 获取设备列表 +const result = await deviceService.getDevices({ + pageNumber: 1, + pageSize: 10, + searchTerm: 'LTE', + deviceType: 'Cellular', + isActive: true +}); + +// 创建设备 +const createResult = await deviceService.createDevice({ + deviceName: '新设备', + deviceCode: 'DEV003', + deviceType: 'Cellular', + description: '测试设备' +}); +``` + +#### 3. 权限管理 +```typescript +// 创建权限 +const result = await permissionService.createPermission({ + name: 'CreateUser', + description: '创建用户的权限' +}); +``` + +#### 4. 角色权限管理 +```typescript +// 获取角色权限 +const result = await rolePermissionService.getRolePermissions('roleId', true); + +// 添加角色权限 +const addResult = await rolePermissionService.addRolePermissions({ + roleId: 'roleId', + permissionIds: ['perm1', 'perm2'] +}); +``` + +### 影响范围 +- **前端服务**:提供了完整的设备运行时、设备、权限、角色权限管理前端服务 +- **API 集成**:与后端控制器完全对应 +- **类型安全**:提供了完整的 TypeScript 类型定义 +- **开发体验**:统一的服务接口,便于前端开发使用 + +### 后续工作建议 +1. **前端页面开发**:基于这些服务开发对应的前端页面 +2. **组件开发**:开发设备运行时状态显示组件 +3. **状态管理**:集成到前端状态管理系统中 +4. **错误处理**:完善错误处理和用户提示 +5. **测试验证**:为所有服务方法添加单元测试 + +## 2025-01-29 更新instrumentService.ts和DevicesView.tsx以匹配后端API + +### 修改原因 +根据后端API的响应结构,需要更新前端的接口定义和组件,确保前后端数据格式一致。主要变化包括: +1. 添加 `runtimeStatus` 字段到设备接口 +2. 移除 `isRunning` 字段(后端没有这个字段) +3. 更新表格显示以包含运行时状态 + +### 修改文件 + +#### 1. 前端服务接口更新 +- `X1.WebUI/src/services/instrumentService.ts` - 更新设备接口定义 + +#### 2. 前端组件更新 +- `X1.WebUI/src/pages/instruments/DevicesView.tsx` - 更新设备管理页面 +- `X1.WebUI/src/pages/instruments/DevicesTable.tsx` - 更新设备表格组件 +- `X1.WebUI/src/pages/instruments/DeviceForm.tsx` - 更新设备表单组件 + +### 修改内容 + +#### 1. 设备接口定义更新 +- **Device接口**: + - 添加 `runtimeStatus: number` 字段(运行时状态,-1表示未知状态) + - 移除 `isRunning: boolean` 字段(后端没有此字段) + - 保持其他字段不变 + +- **CreateDeviceRequest接口**: + - 移除 `isRunning?: boolean` 字段 + - 保持其他字段不变 + +- **UpdateDeviceRequest接口**: + - 移除 `isRunning?: boolean` 字段 + - 保持其他字段不变 + +- **响应接口**: + - 所有响应接口都移除 `isRunning` 字段 + - 保持其他字段不变 + +#### 2. 设备管理页面更新 +- **DevicesView.tsx**: + - 添加运行时状态映射函数:`getRuntimeStatusText` 和 `getRuntimeStatusColor` + - 更新默认列配置,添加运行时状态列 + - 更新注释说明新的字段结构 + - 移除对 `isRunning` 字段的引用 + +#### 3. 设备表格组件更新 +- **DevicesTable.tsx**: + - 添加 `RuntimeStatusBadge` 组件,用于显示运行时状态 + - 更新 `renderCell` 函数,添加对 `runtimeStatus` 字段的处理 + - 更新注释说明新的字段结构 + - 运行时状态显示为徽章形式,不同状态使用不同颜色 + +#### 4. 设备表单组件更新 +- **DeviceForm.tsx**: + - 移除 `isRunning` 字段的初始化和处理 + - 更新表单状态管理,移除对 `isRunning` 的引用 + - 修复表单提交逻辑,移除对 `isRunning` 字段的处理 + +### 技术特性 + +#### 1. 运行时状态映射 +- **状态值**: + - -1: 未知状态 + - 0: 初始化 + - 1: 运行中 + - 2: 停止中 + +- **颜色映射**: + - 未知: 灰色轮廓 + - 初始化: 黄色 + - 运行中: 绿色 + - 停止中: 橙色 + +#### 2. 表格列配置 +- **新增列**:运行时状态列,显示在启用状态列之后 +- **列标题**:启用状态列标题改为"启用状态",新增"运行时状态"列 +- **列顺序**:设备ID、设备名称、设备编码、描述、Agent端口、启用状态、运行时状态、创建时间、操作 + +#### 3. 类型安全 +- 确保所有接口定义与后端API完全匹配 +- 移除不存在的字段引用 +- 添加必要的类型检查和验证 + +### 业务价值 +- **数据一致性**:确保前后端数据格式完全一致 +- **状态可见性**:用户可以在设备列表中看到设备的实时运行状态 +- **用户体验**:通过颜色编码直观显示设备状态 +- **系统稳定性**:避免因字段不匹配导致的错误 + +### 影响范围 +- **API集成**:前端接口定义与后端API完全匹配 +- **用户界面**:设备列表显示运行时状态信息 +- **数据完整性**:确保所有设备数据都能正确显示 +- **开发体验**:提供完整的类型安全和代码提示 + +### 测试建议 +1. 测试设备列表的显示,确认运行时状态正确显示 +2. 验证不同状态的颜色编码是否正确 +3. 测试设备创建和编辑功能,确认表单正常工作 +4. 检查表格列配置,确认列显示顺序正确 +5. 验证搜索和过滤功能是否正常工作 + +## 2025-01-29 修复SearchRuntimesAsync方法,添加设备信息关联查询 + +### 修改原因 +`SearchRuntimesAsync` 方法在查询 `CellularDeviceRuntime` 时没有包含 `Device` 导航属性,导致在 `MapToDto` 方法中访问 `runtime.Device?.Name` 时返回 null。需要修复查询方法,确保能够正确获取设备名称信息。 + +### 修改文件 +- `X1.Infrastructure/Repositories/Device/CellularDeviceRuntimeRepository.cs` - 修复 SearchRuntimesAsync 方法 + +### 修改内容 + +#### 1. 分页查询方法修复 +**SearchRuntimesAsync(分页版本)**: +- 添加 `include: q => q.Include(r => r.Device)` 参数 +- 确保查询结果包含设备信息,支持通过 `DeviceCode` 关联获取 `CellularDevice.Name` + +#### 2. 非分页查询方法修复 +**SearchRuntimesAsync(非分页版本)**: +- 添加 `include: q => q.Include(r => r.Device)` 参数 +- 确保所有搜索查询都能正确获取设备信息 + +### 技术实现 + +#### 1. Entity Framework Include +```csharp +// 修改前 +var result = await QueryRepository.GetPagedAsync(predicate, pageNumber, pageSize, null, cancellationToken); + +// 修改后 +var result = await QueryRepository.GetPagedAsync( + predicate, + pageNumber, + pageSize, + include: q => q.Include(r => r.Device), + cancellationToken); +``` + +#### 2. 导航属性查询 +- 通过 `DeviceCode` 外键关联 `CellularDeviceRuntime` 和 `CellularDevice` +- 使用 `Include(r => r.Device)` 预加载设备信息 +- 确保 `MapToDto` 方法中的 `runtime.Device?.Name` 能够正确获取设备名称 + +### 影响范围 +- **API 响应**:设备运行时状态列表现在能够正确显示设备名称 +- **数据完整性**:确保运行时状态查询结果包含完整的设备信息 +- **用户体验**:前端能够显示设备名称,提供更好的用户界面 + +### 业务价值 +- **信息完整性**:运行时状态列表显示设备名称,便于用户识别 +- **数据关联**:正确建立 `CellularDeviceRuntime` 和 `CellularDevice` 的关联关系 +- **查询性能**:通过 Include 预加载避免 N+1 查询问题 + +## 2025-01-29 修改DeleteDeviceCommandHandler实现软删除CellularDeviceRuntime + ## 2025-01-29 修改DeleteDeviceCommandHandler实现软删除CellularDeviceRuntime ### 修改原因 @@ -3390,4 +4196,207 @@ RuntimeStatus = device.Runtime?.RuntimeStatus != null ? (int)device.Runtime.Runt - **搜索增强**:用户可以通过设备编码快速找到特定设备 - **状态可见性**:单个设备详情页面显示实时运行状态 - **数据完整性**:确保设备信息的完整性和一致性 -- **系统健壮性**:正确处理导航属性和关联数据 \ No newline at end of file +- **系统健壮性**:正确处理导航属性和关联数据 + +## 2025-01-29 简化DeviceRuntimeDto,只保留必要字段 + +### 修改原因 +根据用户需求,简化 `DeviceRuntimeDto` 类,只保留必要的字段: +- DeviceCode(设备编号) +- RuntimeStatus(运行时状态,使用int类型) +- NetworkStackCode(网络栈配置编号) +- Name(设备名称) +- CreatedAt(创建时间) + +### 修改文件 + +#### 1. 响应模型修改 +- `X1.Application/Features/DeviceRuntimes/Queries/GetDeviceRuntimes/GetDeviceRuntimesResponse.cs` - 简化 `DeviceRuntimeDto` 类 + +#### 2. 查询处理器修改 +- `X1.Application/Features/DeviceRuntimes/Queries/GetDeviceRuntimes/GetDeviceRuntimesQueryHandler.cs` - 更新 `MapToDto` 方法 + +### 修改内容 + +#### 1. DeviceRuntimeDto 简化 +**移除的字段**: +- Id(运行时状态ID) +- RuntimeCode(运行编码) +- UpdatedAt(更新时间) +- Device(设备信息对象) + +**保留的字段**: +- DeviceCode(设备编号) +- RuntimeStatus(运行时状态,改为int类型) +- NetworkStackCode(网络栈配置编号) +- Name(设备名称,新增) +- CreatedAt(创建时间) + +#### 2. MapToDto 方法优化 +```csharp +private static DeviceRuntimeDto MapToDto(CellularDeviceRuntime runtime) +{ + return new DeviceRuntimeDto + { + DeviceCode = runtime.DeviceCode, + RuntimeStatus = (int)runtime.RuntimeStatus, + NetworkStackCode = runtime.NetworkStackCode, + Name = runtime.Device?.Name, + CreatedAt = runtime.CreatedAt + }; +} +``` + +### 技术实现 + +#### 1. 类型转换 +- `RuntimeStatus` 从 `string` 改为 `int`,直接使用枚举的整数值 +- 使用 `runtime.Device?.Name` 安全访问设备名称 + +#### 2. 数据简化 +- 移除了复杂的嵌套对象 `DeviceInfoDto` +- 只保留最核心的运行时状态信息 +- 减少了数据传输量,提高API响应性能 + +### 影响范围 +- **API 响应**:设备运行时状态列表接口返回更简洁的数据结构 +- **前端处理**:前端可以更直接地使用运行时状态信息 +- **性能优化**:减少了不必要的数据传输,提高响应速度 +- **数据一致性**:确保返回的数据结构简洁明了 + +### 业务价值 +- **数据精简**:只返回业务必需的核心字段 +- **类型统一**:RuntimeStatus使用int类型,便于前端处理 +- **性能提升**:减少数据传输量,提高API响应速度 +- **维护便利**:简化数据结构,降低维护复杂度 + +## 2025-01-29 为CellularDeviceRuntimeDetail实体添加DeviceCode和NetworkStackCode字段 + +### 修改原因 +根据用户需求,`CellularDeviceRuntimeDetail` 实体需要添加 `DeviceCode` 和 `NetworkStackCode` 字段,用于标识该运行时明细记录属于哪个设备以及使用的网络栈配置。 + +### 修改文件 +- `X1.Domain/Entities/Device/CellularDeviceRuntimeDetail.cs` - 添加DeviceCode和NetworkStackCode字段 + +### 修改内容 + +#### 1. 添加DeviceCode属性 +- **字段名称**:`DeviceCode` +- **字段类型**:`string` +- **约束条件**: + - `[Required]` - 必填字段 + - `[MaxLength(50)]` - 最大长度50个字符 +- **访问修饰符**:`private set` - 只读属性,通过方法修改 +- **位置**:在 `RuntimeCode` 字段之前 + +#### 2. 添加NetworkStackCode属性 +- **字段名称**:`NetworkStackCode` +- **字段类型**:`string` +- **约束条件**: + - `[Required]` - 必填字段 + - `[MaxLength(50)]` - 最大长度50个字符 +- **访问修饰符**:`private set` - 只读属性,通过方法修改 +- **位置**:在 `RuntimeCode` 字段之后 + +#### 3. 更新Create方法 +- **方法签名**:`Create` 方法添加 `deviceCode` 和 `networkStackCode` 参数 +- **参数位置**:`deviceCode` 在 `runtimeCode` 参数之前,`networkStackCode` 在 `runtimeCode` 参数之后 +- **参数验证**:通过 `[Required]` 和 `[MaxLength(50)]` 进行验证 +- **属性赋值**:在创建明细记录时设置 `DeviceCode` 和 `NetworkStackCode` 属性 + +### 技术特性 +- **数据完整性**:设备编码和网络栈配置编号作为必填字段,确保每个运行时明细记录都能关联到具体设备和网络栈配置 +- **长度限制**:最大50个字符,适合各种编码格式 +- **封装性**:使用私有setter,确保数据只能通过方法修改 +- **一致性**:与现有字段保持相同的设计模式 + +### 业务价值 +- **设备关联**:通过设备编码关联运行时明细记录与具体设备 +- **网络栈关联**:通过网络栈配置编号关联运行时明细记录与网络栈配置 +- **数据追溯**:增强运行时明细数据的可追溯性 +- **查询便利**:支持按设备编码和网络栈配置编号查询相关的运行时明细记录 +- **管理便利**:提供更灵活的设备运行时明细管理方式 + +### 影响范围 +- **实体模型**:CellularDeviceRuntimeDetail实体增加了DeviceCode和NetworkStackCode字段 +- **创建操作**:所有创建运行时明细记录的操作都需要提供设备编码和网络栈配置编号 +- **数据验证**:确保设备编码和网络栈配置编号的完整性和有效性 +- **查询逻辑**:支持按设备编码和网络栈配置编号查询运行时明细记录 + +### 后续工作建议 +1. **数据库迁移**:需要创建数据库迁移来添加DeviceCode和NetworkStackCode字段 +2. **仓储更新**:更新相关的仓储方法以支持设备编码和网络栈配置编号字段 +3. **API更新**:更新相关的API接口以支持设备编码和网络栈配置编号字段 +4. **前端界面**:更新前端界面以显示和编辑设备编码和网络栈配置编号 +5. **测试用例**:为设备编码和网络栈配置编号字段添加相应的测试用例 + +## 2025-01-29 调整设备运行时管理路由位置并更新权限配置 + +### 修改原因 +根据用户需求,将设备运行时管理路由从独立路由调整为放在 `instruments` 路由内部,与 `list` 同一层级,同时更新相关的权限配置。 + +### 修改文件 + +#### 1. 路由配置调整 +- `X1.WebUI/src/routes/AppRouter.tsx` - 将设备运行时管理路由移动到 instruments 路由内部 + +#### 2. 菜单配置调整 +- `X1.WebUI/src/constants/menuConfig.ts` - 将设备运行时管理菜单项移动到仪表管理下面 + +#### 3. 权限配置更新 +- `X1.WebUI/src/contexts/AuthContext.tsx` - 在 getDefaultPermissions 函数中添加设备运行时管理权限 + +### 修改内容 + +#### 1. 路由结构调整 +- **原路由结构**: + ``` + /dashboard/device-runtimes/list + /dashboard/device-runtimes/detail/:deviceCode + ``` +- **新路由结构**: + ``` + /dashboard/instruments/device-runtimes/list + /dashboard/instruments/device-runtimes/detail/:deviceCode + ``` + +#### 2. 菜单结构调整 +- **原菜单结构**:设备运行时作为独立顶级菜单项 +- **新菜单结构**:设备运行时作为仪表管理的子菜单项 +- **菜单路径更新**:`/dashboard/device-runtimes/list` → `/dashboard/instruments/device-runtimes/list` + +#### 3. 权限配置更新 +- **新增权限**: + - `deviceruntimes.view` - 查看设备运行时权限 + - `deviceruntimes.manage` - 管理设备运行时权限 +- **权限位置**:添加到 `getDefaultPermissions` 函数中,确保用户登录后能够正确获取设备运行时管理权限 + +### 技术特性 +- **路由嵌套**:设备运行时管理现在嵌套在 instruments 路由下 +- **权限继承**:设备运行时管理继承仪表管理的基础权限结构 +- **菜单层次**:清晰的菜单层次结构,设备运行时管理属于仪表管理范畴 +- **向后兼容**:保持所有现有功能不变,只是调整了路由和菜单结构 + +### 业务价值 +- **逻辑分组**:将设备运行时管理归类到仪表管理下,符合业务逻辑 +- **用户体验**:更清晰的菜单结构,用户更容易找到相关功能 +- **权限管理**:统一的权限管理结构,便于权限分配和管理 +- **系统一致性**:保持系统整体架构的一致性 + +### 影响范围 +- **路由访问**:设备运行时管理页面的访问路径发生变化 +- **菜单导航**:用户需要通过仪表管理菜单访问设备运行时管理 +- **权限控制**:设备运行时管理权限已正确配置 +- **用户体验**:更符合用户预期的菜单结构 + +### 使用方式 +1. 用户登录后,在左侧菜单中点击"仪表管理" +2. 在仪表管理子菜单中点击"运行时状态" +3. 进入设备运行时管理页面:`/dashboard/instruments/device-runtimes/list` +4. 点击设备编号可查看详情:`/dashboard/instruments/device-runtimes/detail/:deviceCode` + +### 注意事项 +- 原有的直接访问路径 `/dashboard/device-runtimes/*` 已失效 +- 需要更新任何硬编码的链接或书签 +- 权限检查仍然正常工作,确保安全性 +- 所有设备运行时管理功能保持不变 \ No newline at end of file