"use client"; import { useCallback, useEffect, useRef, useState } from "react"; import { Button, Chip, Input, Label, ListBox, Modal, Select, Spinner, Table, TextField } from "@heroui/react"; import { Plus, Pencil, PlugConnection, TrashBin } from "@gravity-ui/icons"; import Link from "next/link"; import { api, type ChargePoint } from "@/lib/api"; const statusLabelMap: Record = { Available: "空闲中", Charging: "充电中", Preparing: "准备中", Finishing: "结束中", SuspendedEV: "EV 暂停", SuspendedEVSE: "EVSE 暂停", Reserved: "已预约", Faulted: "故障", Unavailable: "不可用", Occupied: "占用", }; const statusDotClass: Record = { Available: "bg-success", Charging: "bg-accent animate-pulse", Preparing: "bg-warning animate-pulse", Finishing: "bg-warning", SuspendedEV: "bg-warning", SuspendedEVSE: "bg-warning", Reserved: "bg-warning", Faulted: "bg-danger", Unavailable: "bg-danger", Occupied: "bg-warning", }; const registrationColorMap: Record = { Accepted: "success", Pending: "warning", Rejected: "danger", }; type FormData = { chargePointIdentifier: string; chargePointVendor: string; chargePointModel: string; registrationStatus: "Accepted" | "Pending" | "Rejected"; feePerKwh: string; }; const EMPTY_FORM: FormData = { chargePointIdentifier: "", chargePointVendor: "", chargePointModel: "", registrationStatus: "Pending", feePerKwh: "0", }; export default function ChargePointsPage() { const [chargePoints, setChargePoints] = useState([]); const [formOpen, setFormOpen] = useState(false); const [formTarget, setFormTarget] = useState(null); const [formData, setFormData] = useState(EMPTY_FORM); const [formBusy, setFormBusy] = useState(false); const [deleteTarget, setDeleteTarget] = useState(null); const [deleting, setDeleting] = useState(false); const hasFetched = useRef(false); const load = useCallback(async () => { const data = await api.chargePoints.list().catch(() => []); setChargePoints(data); }, []); useEffect(() => { if (!hasFetched.current) { hasFetched.current = true; load(); } }, [load]); const openCreate = () => { setFormTarget(null); setFormData(EMPTY_FORM); setFormOpen(true); }; const openEdit = (cp: ChargePoint) => { setFormTarget(cp); setFormData({ chargePointIdentifier: cp.chargePointIdentifier, chargePointVendor: cp.chargePointVendor ?? "", chargePointModel: cp.chargePointModel ?? "", registrationStatus: cp.registrationStatus as FormData["registrationStatus"], feePerKwh: String(cp.feePerKwh), }); setFormOpen(true); }; const handleSubmit = async () => { if (!formData.chargePointIdentifier.trim()) return; setFormBusy(true); try { const fee = Math.max(0, Math.round(Number(formData.feePerKwh) || 0)); if (formTarget) { // Edit const updated = await api.chargePoints.update(String(formTarget.id), { chargePointVendor: formData.chargePointVendor, chargePointModel: formData.chargePointModel, registrationStatus: formData.registrationStatus, feePerKwh: fee, }); setChargePoints((prev) => prev.map((cp) => (cp.id === formTarget.id ? { ...updated, connectors: cp.connectors } : cp)), ); } else { // Create const created = await api.chargePoints.create({ chargePointIdentifier: formData.chargePointIdentifier.trim(), chargePointVendor: formData.chargePointVendor.trim() || undefined, chargePointModel: formData.chargePointModel.trim() || undefined, registrationStatus: formData.registrationStatus, feePerKwh: fee, }); setChargePoints((prev) => [created, ...prev]); } setFormOpen(false); } finally { setFormBusy(false); } }; const handleDelete = async () => { if (!deleteTarget) return; setDeleting(true); try { await api.chargePoints.delete(String(deleteTarget.id)); setChargePoints((prev) => prev.filter((cp) => cp.id !== deleteTarget.id)); setDeleteTarget(null); } finally { setDeleting(false); } }; const isEdit = formTarget !== null; return (

充电桩管理

共 {chargePoints.length} 台设备

{/* Create / Edit modal */} { if (!formBusy) setFormOpen(open); }}> {isEdit ? "编辑充电桩" : "新建充电桩"} setFormData((f) => ({ ...f, chargePointIdentifier: e.target.value }))} />
setFormData((f) => ({ ...f, chargePointVendor: e.target.value }))} /> setFormData((f) => ({ ...f, chargePointModel: e.target.value }))} />
setFormData((f) => ({ ...f, feePerKwh: e.target.value }))} /> {!isEdit && (

手动创建的充电桩默认注册状态为 Pending,需手动改为 Accepted 后才可正常充电。

)}
标识符 品牌 / 型号 注册状态 电价(分/kWh) 最后心跳 接口状态 {""} {chargePoints.length === 0 && ( 暂无设备 {""} {""} {""} {""} {""} {""} )} {chargePoints.map((cp) => ( {cp.chargePointIdentifier} {cp.chargePointVendor && cp.chargePointModel ? ( `${cp.chargePointVendor} / ${cp.chargePointModel}` ) : ( )} {cp.registrationStatus} {cp.feePerKwh > 0 ? ( {cp.feePerKwh} 分 (¥{(cp.feePerKwh / 100).toFixed(2)}/kWh) ) : ( 免费 )} {cp.lastHeartbeatAt ? ( new Date(cp.lastHeartbeatAt).toLocaleString("zh-CN") ) : ( )}
{cp.connectors.length === 0 ? ( ) : ( [...cp.connectors].sort((a, b) => a.connectorId - b.connectorId).map((conn) => (
#{conn.connectorId} {statusLabelMap[conn.status] ?? conn.status}
)) )}
确认删除充电桩

将删除充电桩{" "} {cp.chargePointIdentifier} 及其所有连接器和充电记录,此操作不可恢复。

))}
); }