import { useEffect, useState, useCallback } from 'react'; import { useNavigate } from 'react-router-dom'; import { Card, Table, Tag, Button, Modal, Form, Input, Select, InputNumber, Space, Row, Col, Statistic, Switch, message } from 'antd'; import { PlusOutlined, EditOutlined, DeleteOutlined, ApiOutlined, CheckCircleOutlined, CloseCircleOutlined, WarningOutlined, AppstoreOutlined } from '@ant-design/icons'; import { getDevices, getDeviceTypes, getDeviceGroups, getDeviceStats, createDevice, updateDevice } from '../../services/api'; import { getDevicePhoto } from '../../utils/devicePhoto'; const statusMap: Record = { online: { color: 'green', text: '在线' }, offline: { color: 'default', text: '离线' }, alarm: { color: 'red', text: '告警' }, maintenance: { color: 'orange', text: '维护' }, }; const protocolOptions = [ { label: 'Modbus TCP', value: 'modbus_tcp' }, { label: 'Modbus RTU', value: 'modbus_rtu' }, { label: 'OPC UA', value: 'opc_ua' }, { label: 'MQTT', value: 'mqtt' }, { label: 'HTTP API', value: 'http_api' }, { label: 'DL/T 645', value: 'dlt645' }, { label: '图像采集', value: 'image' }, ]; export default function Devices() { const navigate = useNavigate(); const [data, setData] = useState({ total: 0, items: [] }); const [stats, setStats] = useState({ online: 0, offline: 0, alarm: 0, total: 0 }); const [deviceTypes, setDeviceTypes] = useState([]); const [deviceGroups, setDeviceGroups] = useState([]); const [loading, setLoading] = useState(true); const [showModal, setShowModal] = useState(false); const [editingDevice, setEditingDevice] = useState(null); const [form] = Form.useForm(); const [filters, setFilters] = useState>({ page: 1, page_size: 20 }); const loadDevices = useCallback(async (params?: Record) => { setLoading(true); try { const query = params || filters; // Remove empty values const cleanQuery: Record = {}; Object.entries(query).forEach(([k, v]) => { if (v !== undefined && v !== null && v !== '') cleanQuery[k] = v; }); const res = await getDevices(cleanQuery); setData(res as any); } catch (e) { console.error(e); } finally { setLoading(false); } }, [filters]); const loadMeta = async () => { try { const [types, groups, st] = await Promise.all([ getDeviceTypes(), getDeviceGroups(), getDeviceStats(), ]); setDeviceTypes(types as any[]); setDeviceGroups(groups as any[]); setStats(st as any); } catch (e) { console.error(e); } }; useEffect(() => { loadMeta(); }, []); useEffect(() => { loadDevices(); }, [filters, loadDevices]); const handleFilterChange = (key: string, value: any) => { setFilters(prev => ({ ...prev, [key]: value, page: 1 })); }; const handlePageChange = (page: number, pageSize: number) => { setFilters(prev => ({ ...prev, page, page_size: pageSize })); }; const openAddModal = () => { setEditingDevice(null); form.resetFields(); form.setFieldsValue({ collect_interval: 15, is_active: true }); setShowModal(true); }; const openEditModal = (record: any) => { setEditingDevice(record); form.setFieldsValue({ ...record, device_type_id: record.device_type_id, device_group_id: record.device_group_id, connection_params: record.connection_params ? JSON.stringify(record.connection_params, null, 2) : '', }); setShowModal(true); }; const handleSubmit = async (values: any) => { try { // Parse connection_params if provided as string if (values.connection_params && typeof values.connection_params === 'string') { try { values.connection_params = JSON.parse(values.connection_params); } catch { message.error('连接参数JSON格式错误'); return; } } if (editingDevice) { await updateDevice(editingDevice.id, values); message.success('设备更新成功'); } else { await createDevice(values); message.success('设备创建成功'); } setShowModal(false); form.resetFields(); loadDevices(); loadMeta(); } catch (e: any) { message.error(e?.detail || '操作失败'); } }; const columns = [ { title: '', dataIndex: 'device_type', width: 50, render: (_: any, record: any) => ( )}, { title: '设备名称', dataIndex: 'name', width: 160, ellipsis: true, render: (name: string, record: any) => ( navigate(`/devices/${record.id}`)}>{name} )}, { title: '设备编号', dataIndex: 'code', width: 130 }, { title: '设备类型', dataIndex: 'device_type_name', width: 120, render: (v: string) => v ? } color="blue">{v} : '-' }, { title: '设备分组', dataIndex: 'device_group_name', width: 120 }, { title: '型号', dataIndex: 'model', width: 120, ellipsis: true }, { title: '厂商', dataIndex: 'manufacturer', width: 120, ellipsis: true }, { title: '额定功率(kW)', dataIndex: 'rated_power', width: 110, render: (v: number) => v != null ? v : '-' }, { title: '位置', dataIndex: 'location', width: 120, ellipsis: true }, { title: '协议', dataIndex: 'protocol', width: 100 }, { title: '状态', dataIndex: 'status', width: 80, render: (s: string) => { const st = statusMap[s] || { color: 'default', text: s || '-' }; return {st.text}; }}, { title: '最近数据时间', dataIndex: 'last_data_time', width: 170 }, { title: '操作', key: 'action', width: 120, fixed: 'right' as const, render: (_: any, record: any) => ( )}, ]; return (
{/* Stats Cards */} } /> } valueStyle={{ color: '#52c41a' }} /> } valueStyle={{ color: '#999' }} /> } valueStyle={{ color: '#ff4d4f' }} /> {/* Device Table */} } onClick={openAddModal}>添加设备 }> {/* Filters */} ({ label: g.name, value: g.id }))} onChange={v => handleFilterChange('device_group', v)} /> ({ label: g.name, value: g.id }))} />