import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { Table, Button, Input, Space, Drawer, Form, Select, App, Tooltip, Switch, Badge, Typography, Tag, Row, Col, } from 'antd'; import { PlusOutlined, SaveOutlined, DeleteOutlined, EditOutlined, SearchOutlined, SyncOutlined, MonitorOutlined, ReloadOutlined, LinkOutlined, WifiOutlined, CheckCircleOutlined, SafetyCertificateOutlined, } from '@ant-design/icons'; import httpService from '../../services/httpService'; import { buildApiUrl, API_ENDPOINTS } from '../../config/api'; import AdminModuleShell from '../../components/AdminModuleShell'; import ActionButton from '../../components/ActionButton'; import useSystemPageSize from '../../hooks/useSystemPageSize'; const { Text } = Typography; const { TextArea } = Input; const ONLINE_MINUTES = 10; const TerminalManagement = () => { const { message, modal } = App.useApp(); const [form] = Form.useForm(); const pageSize = useSystemPageSize(10); const [terminals, setTerminals] = useState([]); const [terminalTypes, setTerminalTypes] = useState([]); const [loading, setLoading] = useState(true); const [keyword, setKeyword] = useState(''); const [filterType, setFilterType] = useState('all'); const [filterStatus, setFilterStatus] = useState('all'); const [filterActivation, setFilterActivation] = useState('all'); const [showDrawer, setShowDrawer] = useState(false); const [isEditing, setIsEditing] = useState(false); const [selectedTerminal, setSelectedTerminal] = useState(null); const fetchTerminalTypes = useCallback(async () => { try { const response = await httpService.get(buildApiUrl(API_ENDPOINTS.DICT_DATA.BY_TYPE('client_platform')), { params: { parent_code: 'TERMINAL' }, }); if (response.code === '200') { setTerminalTypes(response.data.items || []); } } catch (error) { console.error('Failed to fetch terminal types:', error); } }, []); const fetchTerminals = useCallback(async () => { setLoading(true); try { const response = await httpService.get(buildApiUrl(API_ENDPOINTS.TERMINALS.LIST), { params: { page: 1, size: 10000 }, }); if (response.code === '200') { setTerminals(response.data.items || []); } } catch { message.error('获取终端列表失败'); } finally { setLoading(false); } }, [message]); useEffect(() => { fetchTerminalTypes(); fetchTerminals(); }, [fetchTerminalTypes, fetchTerminals]); const handleOpenModal = (terminal = null) => { if (terminal) { setIsEditing(true); setSelectedTerminal(terminal); form.setFieldsValue({ ...terminal, status: terminal.status === 1, }); } else { setIsEditing(false); setSelectedTerminal(null); form.resetFields(); form.setFieldsValue({ terminal_type: terminalTypes[0]?.dict_code, status: true, }); } setShowDrawer(true); }; const handleSave = async () => { try { const values = await form.validateFields(); const payload = { ...values, status: values.status ? 1 : 0, }; if (isEditing) { await httpService.put(buildApiUrl(API_ENDPOINTS.TERMINALS.UPDATE(selectedTerminal.id)), payload); message.success('终端更新成功'); } else { await httpService.post(buildApiUrl(API_ENDPOINTS.TERMINALS.CREATE), payload); message.success('终端创建成功'); } setShowDrawer(false); fetchTerminals(); } catch (error) { if (!error.errorFields) { message.error('保存失败'); } } }; const handleDelete = (item) => { modal.confirm({ title: '删除终端', content: `确定要删除 IMEI 为 ${item.imei} 的终端吗?`, okText: '确定', okType: 'danger', onOk: async () => { try { await httpService.delete(buildApiUrl(API_ENDPOINTS.TERMINALS.DELETE(item.id))); message.success('删除成功'); fetchTerminals(); } catch { message.error('删除失败'); } }, }); }; const handleToggleStatus = async (item, checked) => { try { const newStatus = checked ? 1 : 0; await httpService.post(buildApiUrl(API_ENDPOINTS.TERMINALS.STATUS(item.id)), null, { params: { status: newStatus }, }); setTerminals((prev) => prev.map((terminal) => ( terminal.id === item.id ? { ...terminal, status: newStatus } : terminal ))); message.success(`已${newStatus === 1 ? '启用' : '停用'}终端`); } catch { message.error('状态更新失败'); } }; const getTerminalTypeLabel = (code) => ( terminalTypes.find((terminalType) => terminalType.dict_code === code)?.label_cn || code ); const isOnline = (lastOnlineAt) => { if (!lastOnlineAt) return false; const diffMs = Date.now() - new Date(lastOnlineAt).getTime(); return diffMs >= 0 && diffMs <= ONLINE_MINUTES * 60 * 1000; }; const filteredTerminals = useMemo(() => terminals.filter((terminal) => { if (filterType !== 'all' && terminal.terminal_type !== filterType) { return false; } if (filterStatus !== 'all' && terminal.status !== Number.parseInt(filterStatus, 10)) { return false; } if (filterActivation === 'activated' && terminal.is_activated !== 1) { return false; } if (filterActivation === 'pending' && terminal.is_activated === 1) { return false; } if (!keyword) { return true; } const query = keyword.toLowerCase(); return [ terminal.imei, terminal.terminal_name, terminal.current_username, terminal.current_user_caption, terminal.mac_address, terminal.ip_address, ].some((field) => String(field || '').toLowerCase().includes(query)); }), [terminals, filterType, filterStatus, filterActivation, keyword]); const activeCount = useMemo(() => terminals.filter((terminal) => terminal.status === 1).length, [terminals]); const activatedCount = useMemo(() => terminals.filter((terminal) => terminal.is_activated === 1).length, [terminals]); const onlineCount = useMemo(() => terminals.filter((terminal) => isOnline(terminal.last_online_at)).length, [terminals]); const columns = [ { title: 'IMEI', dataIndex: 'imei', key: 'imei', width: 160, render: (text) => {text}, }, { title: '终端信息', key: 'terminal', width: 220, render: (_, record) => ( {record.terminal_name || '未命名终端'} {getTerminalTypeLabel(record.terminal_type)} {isOnline(record.last_online_at) ? }>在线 : 离线} ), }, { title: '绑定账号', key: 'user', width: 170, render: (_, record) => ( record.current_user_caption ? ( {record.current_user_caption} ({record.current_username}) ) : ( 未绑定 ) ), }, { title: '网络与固件', key: 'network', width: 220, render: (_, record) => ( IP:{record.ip_address || '-'} MAC:{record.mac_address || '-'} 固件:{record.firmware_version || '-'} ), }, { title: '状态', key: 'status', width: 110, render: (_, record) => ( handleToggleStatus(record, checked)} /> ), }, { title: '激活情况', dataIndex: 'is_activated', key: 'is_activated', width: 120, render: (value) => ( value === 1 ? : ), }, { title: '最后在线', dataIndex: 'last_online_at', key: 'last_online_at', width: 180, render: (time) => (time ? new Date(time).toLocaleString() : '-'), }, { title: '操作', key: 'action', fixed: 'right', width: 100, render: (_, record) => ( } onClick={() => handleOpenModal(record)} /> } onClick={() => handleDelete(record)} /> ), }, ]; return (
} title="终端管理" subtitle="维护终端设备全生命周期:注册、绑定、激活、启停与在线状态监控。" rightActions={( )} stats={[ { label: '设备总数', value: terminals.length, icon: , tone: 'blue', desc: '已纳入系统管理的终端设备总量', }, { label: '启用设备', value: activeCount, icon: , tone: 'green', desc: '允许接入平台并正常提供服务的设备', }, { label: '已激活设备', value: activatedCount, icon: , tone: 'violet', desc: '已完成激活或完成初始化绑定的设备', }, { label: '在线设备', value: onlineCount, icon: , tone: 'cyan', desc: `最近 ${ONLINE_MINUTES} 分钟内有心跳上报的设备`, }, ]} toolbar={( } value={keyword} onChange={(e) => setKeyword(e.target.value)} style={{ width: 280 }} allowClear />