diff --git a/src/X1.WebUI/src/constants/menuConfig.ts b/src/X1.WebUI/src/constants/menuConfig.ts index bc29579..f9a80ca 100644 --- a/src/X1.WebUI/src/constants/menuConfig.ts +++ b/src/X1.WebUI/src/constants/menuConfig.ts @@ -173,7 +173,7 @@ export const menuItems: MenuItem[] = [ children: [ { title: '设备列表', - href: '/dashboard/instruments/devices', + href: '/dashboard/instruments/list', permission: 'devices.view', }, { diff --git a/src/X1.WebUI/src/pages/instruments/ConfigsTable.tsx b/src/X1.WebUI/src/pages/configs/ConfigsTable.tsx similarity index 99% rename from src/X1.WebUI/src/pages/instruments/ConfigsTable.tsx rename to src/X1.WebUI/src/pages/configs/ConfigsTable.tsx index b51885d..6eb67fc 100644 --- a/src/X1.WebUI/src/pages/instruments/ConfigsTable.tsx +++ b/src/X1.WebUI/src/pages/configs/ConfigsTable.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { Button } from '@/components/ui/button'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'; -import { Config, ConfigType } from '@/services/instrumentService'; +import { Config, ConfigType } from '@/services/configService'; import { Badge } from '@/components/ui/badge'; import { EyeOpenIcon, CheckIcon, PlayIcon } from '@radix-ui/react-icons'; diff --git a/src/X1.WebUI/src/pages/instruments/ConfigsView.tsx b/src/X1.WebUI/src/pages/configs/ConfigsView.tsx similarity index 97% rename from src/X1.WebUI/src/pages/instruments/ConfigsView.tsx rename to src/X1.WebUI/src/pages/configs/ConfigsView.tsx index 69f940e..37a31ae 100644 --- a/src/X1.WebUI/src/pages/instruments/ConfigsView.tsx +++ b/src/X1.WebUI/src/pages/configs/ConfigsView.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useState } from 'react'; -import { instrumentService, Config } from '@/services/instrumentService'; +import { configService, Config } from '@/services/configService'; import ConfigsTable from './ConfigsTable'; import { Input } from '@/components/ui/input'; import PaginationBar from '@/components/ui/PaginationBar'; @@ -68,7 +68,7 @@ export default function ConfigsView() { const fetchConfigs = async (params = {}) => { setLoading(true); - const result = await instrumentService.getAllConfigs({ configId, page, pageSize, ...params }); + const result = await configService.getAllConfigs({ configId, page, pageSize, ...params }); if (result.isSuccess && result.data) { setConfigs(result.data.configs || []); setTotal(result.data.totalCount || 0); diff --git a/src/X1.WebUI/src/pages/instruments/DevicesTable.tsx b/src/X1.WebUI/src/pages/instruments/DevicesTable.tsx index c02668c..32cb927 100644 --- a/src/X1.WebUI/src/pages/instruments/DevicesTable.tsx +++ b/src/X1.WebUI/src/pages/instruments/DevicesTable.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { Button } from '@/components/ui/button'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'; -import { Device, DeviceStatus, DeviceType } from '@/services/instrumentService'; +import { Device, DeviceStatus, DeviceType } from '@/services/deviceService'; import { Badge } from '@/components/ui/badge'; import { EyeOpenIcon, PlayIcon } from '@radix-ui/react-icons'; diff --git a/src/X1.WebUI/src/pages/instruments/DevicesView.tsx b/src/X1.WebUI/src/pages/instruments/DevicesView.tsx index ff09a43..e304ffe 100644 --- a/src/X1.WebUI/src/pages/instruments/DevicesView.tsx +++ b/src/X1.WebUI/src/pages/instruments/DevicesView.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useState } from 'react'; -import { instrumentService, Device } from '@/services/instrumentService'; +import { deviceService, Device } from '@/services/deviceService'; import DevicesTable from './DevicesTable'; import { Input } from '@/components/ui/input'; import PaginationBar from '@/components/ui/PaginationBar'; @@ -65,7 +65,7 @@ export default function DevicesView() { const fetchDevices = async (params = {}) => { setLoading(true); - const result = await instrumentService.getAllDevices({ deviceId, page, pageSize, ...params }); + const result = await deviceService.getAllDevices({ deviceId, page, pageSize, ...params }); if (result.isSuccess && result.data) { setDevices(result.data.devices || []); setTotal(result.data.totalCount || 0); diff --git a/src/X1.WebUI/src/pages/instruments/ProtocolsTable.tsx b/src/X1.WebUI/src/pages/instruments/ProtocolsTable.tsx deleted file mode 100644 index cbf4e1f..0000000 --- a/src/X1.WebUI/src/pages/instruments/ProtocolsTable.tsx +++ /dev/null @@ -1,254 +0,0 @@ -import React from 'react'; -import { Button } from '@/components/ui/button'; -import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'; -import { Protocol, ProtocolType } from '@/services/instrumentService'; -import { Badge } from '@/components/ui/badge'; -import { EyeOpenIcon, FileTextIcon } from '@radix-ui/react-icons'; - -interface ProtocolsTableProps { - protocols: Protocol[]; - loading: boolean; - onView: (protocol: Protocol) => void; - onViewDocumentation: (protocol: Protocol) => void; - page: number; - pageSize: number; - total: number; - onPageChange: (page: number) => void; - hideCard?: boolean; - density?: 'compact' | 'default' | 'comfortable'; - columns?: { key: string; title: string; visible: boolean }[]; -} - -// 协议类型徽章组件 -const ProtocolTypeBadge: React.FC<{ type: ProtocolType }> = ({ type }) => { - const typeConfig = { - modbus: { label: 'Modbus', className: 'bg-blue-100 text-blue-800' }, - opcua: { label: 'OPC UA', className: 'bg-green-100 text-green-800' }, - mqtt: { label: 'MQTT', className: 'bg-purple-100 text-purple-800' }, - http: { label: 'HTTP', className: 'bg-orange-100 text-orange-800' }, - tcp: { label: 'TCP', className: 'bg-indigo-100 text-indigo-800' }, - udp: { label: 'UDP', className: 'bg-teal-100 text-teal-800' }, - serial: { label: 'Serial', className: 'bg-gray-100 text-gray-800' }, - }; - - const config = typeConfig[type]; - return ( - - {config.label} - - ); -}; - -// 状态徽章组件 -const StatusBadge: React.FC<{ isActive: boolean }> = ({ isActive }) => { - return ( - - {isActive ? '启用' : '禁用'} - - ); -}; - -// 安全级别徽章组件 -const SecurityLevelBadge: React.FC<{ level: 'low' | 'medium' | 'high' }> = ({ level }) => { - const levelConfig = { - low: { label: '低', className: 'bg-green-100 text-green-800' }, - medium: { label: '中', className: 'bg-yellow-100 text-yellow-800' }, - high: { label: '高', className: 'bg-red-100 text-red-800' }, - }; - - const config = levelConfig[level]; - return ( - - {config.label} - - ); -}; - -// 加密类型徽章组件 -const EncryptionTypeBadge: React.FC<{ type: 'none' | 'ssl' | 'tls' | 'aes' }> = ({ type }) => { - const typeConfig = { - none: { label: '无', className: 'bg-gray-100 text-gray-800' }, - ssl: { label: 'SSL', className: 'bg-blue-100 text-blue-800' }, - tls: { label: 'TLS', className: 'bg-green-100 text-green-800' }, - aes: { label: 'AES', className: 'bg-purple-100 text-purple-800' }, - }; - - const config = typeConfig[type]; - return ( - - {config.label} - - ); -}; - -// 认证类型徽章组件 -const AuthenticationTypeBadge: React.FC<{ type: 'none' | 'basic' | 'token' | 'certificate' }> = ({ type }) => { - const typeConfig = { - none: { label: '无', className: 'bg-gray-100 text-gray-800' }, - basic: { label: '基本', className: 'bg-blue-100 text-blue-800' }, - token: { label: '令牌', className: 'bg-green-100 text-green-800' }, - certificate: { label: '证书', className: 'bg-purple-100 text-purple-800' }, - }; - - const config = typeConfig[type]; - return ( - - {config.label} - - ); -}; - -// 数据格式徽章组件 -const DataFormatBadge: React.FC<{ format: 'json' | 'xml' | 'binary' | 'csv' }> = ({ format }) => { - const formatConfig = { - json: { label: 'JSON', className: 'bg-blue-100 text-blue-800' }, - xml: { label: 'XML', className: 'bg-green-100 text-green-800' }, - binary: { label: 'Binary', className: 'bg-purple-100 text-purple-800' }, - csv: { label: 'CSV', className: 'bg-orange-100 text-orange-800' }, - }; - - const config = formatConfig[format]; - return ( - - {config.label} - - ); -}; - -export default function ProtocolsTable({ - protocols, - loading, - onView, - onViewDocumentation, - page, - pageSize, - total, - onPageChange, - hideCard = false, - density = 'default', - columns = [] -}: ProtocolsTableProps) { - const densityClasses = { - compact: 'py-1', - default: 'py-2', - comfortable: 'py-3', - }; - - const visibleColumns = columns.filter(col => col.visible); - - const renderCell = (protocol: Protocol, columnKey: string) => { - switch (columnKey) { - case 'protocolId': - return ( -
- {protocol.protocolId} -
- ); - case 'protocolName': - return ( -
- {protocol.protocolName} -
- ); - case 'protocolType': - return ; - case 'version': - return {protocol.version}; - case 'isActive': - return ; - case 'defaultPort': - return {protocol.defaultPort}; - case 'securityLevel': - return ; - case 'encryptionType': - return ; - case 'authenticationType': - return ; - case 'connectionTimeout': - return {protocol.connectionTimeout}ms; - case 'dataFormat': - return ; - case 'createdBy': - return {protocol.createdBy}; - case 'actions': - return ( -
- - -
- ); - default: - return null; - } - }; - - if (loading) { - return ( -
-
-
-

加载中...

-
-
- ); - } - - if (protocols.length === 0) { - return ( -
-
-

暂无协议数据

-
-
- ); - } - - const tableContent = ( - - - - {visibleColumns.map((column) => ( - - {column.title} - - ))} - - - - {protocols.map((protocol) => ( - - {visibleColumns.map((column) => ( - - {renderCell(protocol, column.key)} - - ))} - - ))} - -
- ); - - if (hideCard) { - return tableContent; - } - - return ( -
- {tableContent} -
- ); -} \ No newline at end of file diff --git a/src/X1.WebUI/src/pages/instruments/ProtocolsView.tsx b/src/X1.WebUI/src/pages/instruments/ProtocolsView.tsx deleted file mode 100644 index ddcdd66..0000000 --- a/src/X1.WebUI/src/pages/instruments/ProtocolsView.tsx +++ /dev/null @@ -1,209 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { instrumentService, Protocol } from '@/services/instrumentService'; -import ProtocolsTable from './ProtocolsTable'; -import { Input } from '@/components/ui/input'; -import PaginationBar from '@/components/ui/PaginationBar'; -import TableToolbar, { DensityType } from '@/components/ui/TableToolbar'; -import { ChevronDownIcon, ChevronUpIcon } from '@radix-ui/react-icons'; - -const defaultColumns = [ - { key: 'protocolId', title: '协议ID', visible: true }, - { key: 'protocolName', title: '协议名称', visible: true }, - { key: 'protocolType', title: '协议类型', visible: true }, - { key: 'version', title: '版本', visible: true }, - { key: 'isActive', title: '状态', visible: true }, - { key: 'defaultPort', title: '默认端口', visible: true }, - { key: 'securityLevel', title: '安全级别', visible: true }, - { key: 'encryptionType', title: '加密类型', visible: true }, - { key: 'authenticationType', title: '认证类型', visible: true }, - { key: 'connectionTimeout', title: '连接超时(ms)', visible: true }, - { key: 'dataFormat', title: '数据格式', visible: true }, - { key: 'createdBy', 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: 'protocolId', label: '协议ID', type: 'input', placeholder: '请输入' }, - { key: 'protocolType', label: '协议类型', type: 'select', options: [ - { value: '', label: '请选择' }, - { value: 'modbus', label: 'Modbus' }, - { value: 'opcua', label: 'OPC UA' }, - { value: 'mqtt', label: 'MQTT' }, - { value: 'http', label: 'HTTP' }, - { value: 'tcp', label: 'TCP' }, - { value: 'udp', label: 'UDP' }, - { value: 'serial', label: 'Serial' }, - ] }, - { key: 'isActive', label: '状态', type: 'select', options: [ - { value: '', label: '请选择' }, - { value: 'true', label: '启用' }, - { value: 'false', label: '禁用' }, - ] }, -]; - -// 高级字段(展开时才显示) -const advancedFields: SearchField[] = [ - { key: 'version', label: '版本', type: 'input', placeholder: '请输入' }, - { key: 'createdBy', label: '创建人', type: 'input', placeholder: '请输入' }, -]; - -export default function ProtocolsView() { - const [protocols, setProtocols] = useState([]); - const [loading, setLoading] = useState(false); - const [total, setTotal] = useState(0); - const [protocolId, setProtocolId] = useState(''); - const [page, setPage] = useState(1); - const [pageSize, setPageSize] = useState(10); - const [density, setDensity] = useState('default'); - const [columns, setColumns] = useState(defaultColumns); - const [showAdvanced, setShowAdvanced] = useState(false); - - const fetchProtocols = async (params = {}) => { - setLoading(true); - const result = await instrumentService.getAllProtocols({ protocolId, page, pageSize, ...params }); - if (result.isSuccess && result.data) { - setProtocols(result.data.protocols || []); - setTotal(result.data.totalCount || 0); - } - setLoading(false); - }; - - useEffect(() => { - fetchProtocols(); - // eslint-disable-next-line - }, [page, pageSize]); - - const handleView = (protocol: Protocol) => { - // 这里可以实现查看协议详情的逻辑 - console.log('查看协议:', protocol); - }; - - const handleViewDocumentation = (protocol: Protocol) => { - // 这里可以实现查看协议文档的逻辑 - console.log('查看协议文档:', protocol); - }; - - // 查询按钮 - const handleQuery = () => { - setPage(1); - fetchProtocols({ page: 1 }); - }; - - // 重置按钮 - const handleReset = () => { - setProtocolId(''); - setPage(1); - fetchProtocols({ protocolId: '', page: 1 }); - }; - - // 每页条数选择 - const handlePageSizeChange = (size: number) => { - setPageSize(size); - setPage(1); - }; - - const totalPages = Math.ceil(total / pageSize); - - return ( -
-
- {/* 丰富美化后的搜索栏 */} -
-
{ - e.preventDefault(); - handleQuery(); - }} - > - {(showAdvanced ? [...firstRowFields, ...advancedFields] : firstRowFields).map(field => ( -
- - {field.type === 'input' && ( - ) => { - if (field.key === 'protocolId') setProtocolId(e.target.value); - }} - /> - )} - {field.type === 'select' && ( - - )} -
- ))} - {/* 按钮组直接作为表单项之一,紧跟在最后一个表单项后面 */} -
- - - -
-
-
- {/* 表格整体卡片区域,包括工具栏、表格、分页 */} -
- {/* 顶部工具栏 */} -
- fetchProtocols()} - onDensityChange={setDensity} - onColumnsChange={setColumns} - onColumnsReset={() => setColumns(defaultColumns)} - columns={columns} - density={density} - /> -
- {/* 表格区域 */} - - {/* 分页 */} - -
-
-
- ); -} \ No newline at end of file diff --git a/src/X1.WebUI/src/pages/protocols/ProtocolForm.tsx b/src/X1.WebUI/src/pages/protocols/ProtocolForm.tsx new file mode 100644 index 0000000..f25af5e --- /dev/null +++ b/src/X1.WebUI/src/pages/protocols/ProtocolForm.tsx @@ -0,0 +1,109 @@ +import React 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 { Checkbox } from '@/components/ui/checkbox'; +import { CreateProtocolVersionRequest, UpdateProtocolVersionRequest } from '@/services/protocolService'; + +interface ProtocolFormProps { + onSubmit: (data: CreateProtocolVersionRequest | UpdateProtocolVersionRequest) => void; + initialData?: Partial; + isEdit?: boolean; + isSubmitting?: boolean; +} + +export default function ProtocolForm({ onSubmit, initialData, isEdit = false, isSubmitting = false }: ProtocolFormProps) { + const [formData, setFormData] = React.useState({ + name: initialData?.name || '', + version: initialData?.version || '', + description: initialData?.description || '', + isEnabled: initialData?.isEnabled ?? true, + releaseDate: initialData?.releaseDate || '', + minimumSupportedVersion: initialData?.minimumSupportedVersion || '' + }); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + if (isSubmitting) return; // 防止重复提交 + onSubmit(formData); + }; + + return ( +
+
+ + setFormData({ ...formData, name: e.target.value })} + placeholder="请输入协议名称" + required + disabled={isSubmitting} + /> +
+ +
+ + setFormData({ ...formData, version: e.target.value })} + placeholder="例如: 1.0.0" + required + disabled={isSubmitting} + /> +
+ +
+ +