import { useState, useCallback, useMemo } from 'react'; import { Button, Form, Input, message, Modal, Popconfirm, Select, Space, Table, Tag, } from 'antd'; import type { ColumnsType } from 'antd/es/table'; import dayjs from 'dayjs'; import { bleGatewayApi, type BleGateway, type CreateBleGatewayReq, type UpdateBleGatewayReq, GATEWAY_STATUS_OPTIONS, GATEWAY_STATUS_COLOR, GATEWAY_STATUS_LABEL, } from '../../api/health/bleGateways'; import { PageContainer } from '../../components/PageContainer'; import { usePermission } from '../../hooks/usePermission'; import { useNavigate } from 'react-router-dom'; export default function BleGatewayList() { const { hasPermission } = usePermission('health.ble-gateways.manage'); const navigate = useNavigate(); const [data, setData] = useState([]); const [total, setTotal] = useState(0); const [page, setPage] = useState(1); const [loading, setLoading] = useState(false); const [statusFilter, setStatusFilter] = useState(); const [modalOpen, setModalOpen] = useState(false); const [editRecord, setEditRecord] = useState(null); const [submitting, setSubmitting] = useState(false); const [form] = Form.useForm(); const pageSize = 20; const fetchData = useCallback(async (p: number, status?: string) => { setLoading(true); try { const resp = await bleGatewayApi.list({ page: p, page_size: pageSize, status }); setData(resp.data); setTotal(resp.total); setPage(p); } catch { message.error('加载网关列表失败'); } finally { setLoading(false); } }, []); const handleSearch = () => { fetchData(1, statusFilter); }; const handleCreate = () => { setEditRecord(null); form.resetFields(); setModalOpen(true); }; const handleEdit = (record: BleGateway) => { setEditRecord(record); form.setFieldsValue({ name: record.name, status: record.status, firmware_version: record.firmware_version, }); setModalOpen(true); }; const handleSubmit = async () => { try { const values = await form.validateFields(); setSubmitting(true); if (editRecord) { const req: UpdateBleGatewayReq & { version: number } = { ...values, version: editRecord.version, }; await bleGatewayApi.update(editRecord.gateway_id, req); message.success('网关已更新'); } else { const req: CreateBleGatewayReq = values; const created = await bleGatewayApi.create(req); Modal.success({ title: '网关创建成功', content: (

网关 ID:{created.gateway_id}

API Key:

请妥善保存 API Key,关闭后无法再次查看明文。

), width: 520, }); } setModalOpen(false); fetchData(page, statusFilter); } catch { // validation } finally { setSubmitting(false); } }; const handleDelete = async (record: BleGateway) => { try { await bleGatewayApi.delete(record.gateway_id, record.version); message.success('网关已删除'); fetchData(page, statusFilter); } catch { message.error('删除失败'); } }; const handleRegenerateKey = async (record: BleGateway) => { try { const updated = await bleGatewayApi.regenerateKey(record.gateway_id); Modal.success({ title: 'API Key 已重新生成', content: (

新 API Key:

旧 Key 已失效,请更新网关配置。

), width: 520, }); fetchData(page, statusFilter); } catch { message.error('重新生成密钥失败'); } }; const columns: ColumnsType = useMemo(() => [ { title: '网关名称', dataIndex: 'name', width: 180, render: (v: string, record) => ( navigate(`/health/ble-gateways/${record.gateway_id}`)}>{v} ), }, { title: '网关 ID', dataIndex: 'gateway_id', width: 160, ellipsis: true, }, { title: '状态', dataIndex: 'status', width: 100, render: (v: string) => ( {GATEWAY_STATUS_LABEL[v] ?? v} ), }, { title: '固件版本', dataIndex: 'firmware_version', width: 110, render: (v: string) => v ?? '-', }, { title: 'IP 地址', dataIndex: 'ip_address', width: 130, render: (v: string) => v ?? '-', }, { title: '绑定患者', dataIndex: 'patient_count', width: 90, render: (v: number) => v ?? 0, }, { title: '最后心跳', dataIndex: 'last_heartbeat_at', width: 170, render: (v: string) => v ? dayjs(v).format('YYYY-MM-DD HH:mm:ss') : '-', }, { title: '操作', width: 220, render: (_, record) => ( handleRegenerateKey(record)}> handleDelete(record)}> ), }, ], [page, statusFilter, navigate]); if (!hasPermission) { return (
权限不足
); } return ( 添加网关} > )} {editRecord && ( ); }