204 lines
8.1 KiB
TypeScript
204 lines
8.1 KiB
TypeScript
|
|
import { useEffect, useState, useCallback } from 'react';
|
||
|
|
import { Card, Table, Tag, Button, Modal, Form, Input, Select, InputNumber, Space, message } from 'antd';
|
||
|
|
import { PlusOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons';
|
||
|
|
import { getChargingPiles, createChargingPile, updateChargingPile, deleteChargingPile, getChargingStations, getChargingBrands } from '../../services/api';
|
||
|
|
|
||
|
|
const workStatusMap: Record<string, { color: string; text: string }> = {
|
||
|
|
idle: { color: 'green', text: '空闲' },
|
||
|
|
charging: { color: 'blue', text: '充电中' },
|
||
|
|
fault: { color: 'red', text: '故障' },
|
||
|
|
offline: { color: 'default', text: '离线' },
|
||
|
|
};
|
||
|
|
|
||
|
|
const typeOptions = [
|
||
|
|
{ label: '交流慢充', value: 'AC_slow' },
|
||
|
|
{ label: '直流快充', value: 'DC_fast' },
|
||
|
|
{ label: '直流超充', value: 'DC_superfast' },
|
||
|
|
];
|
||
|
|
|
||
|
|
const connectorOptions = [
|
||
|
|
{ label: 'GB/T', value: 'GB_T' },
|
||
|
|
{ label: 'CCS', value: 'CCS' },
|
||
|
|
{ label: 'CHAdeMO', value: 'CHAdeMO' },
|
||
|
|
];
|
||
|
|
|
||
|
|
export default function Piles() {
|
||
|
|
const [data, setData] = useState<any>({ total: 0, items: [] });
|
||
|
|
const [stations, setStations] = useState<any[]>([]);
|
||
|
|
const [brands, setBrands] = useState<any[]>([]);
|
||
|
|
const [loading, setLoading] = useState(true);
|
||
|
|
const [showModal, setShowModal] = useState(false);
|
||
|
|
const [editing, setEditing] = useState<any>(null);
|
||
|
|
const [form] = Form.useForm();
|
||
|
|
const [filters, setFilters] = useState<Record<string, any>>({ page: 1, page_size: 20 });
|
||
|
|
|
||
|
|
const loadData = useCallback(async () => {
|
||
|
|
setLoading(true);
|
||
|
|
try {
|
||
|
|
const cleanQuery: Record<string, any> = {};
|
||
|
|
Object.entries(filters).forEach(([k, v]) => {
|
||
|
|
if (v !== undefined && v !== null && v !== '') cleanQuery[k] = v;
|
||
|
|
});
|
||
|
|
const res = await getChargingPiles(cleanQuery);
|
||
|
|
setData(res as any);
|
||
|
|
} catch { message.error('加载充电桩失败'); }
|
||
|
|
finally { setLoading(false); }
|
||
|
|
}, [filters]);
|
||
|
|
|
||
|
|
const loadMeta = async () => {
|
||
|
|
try {
|
||
|
|
const [st, br] = await Promise.all([
|
||
|
|
getChargingStations({ page_size: 100 }),
|
||
|
|
getChargingBrands(),
|
||
|
|
]);
|
||
|
|
setStations((st as any).items || []);
|
||
|
|
setBrands(br as any[]);
|
||
|
|
} catch {}
|
||
|
|
};
|
||
|
|
|
||
|
|
useEffect(() => { loadMeta(); }, []);
|
||
|
|
useEffect(() => { loadData(); }, [filters, loadData]);
|
||
|
|
|
||
|
|
const handleFilterChange = (key: string, value: any) => {
|
||
|
|
setFilters(prev => ({ ...prev, [key]: value, page: 1 }));
|
||
|
|
};
|
||
|
|
|
||
|
|
const openAddModal = () => {
|
||
|
|
setEditing(null);
|
||
|
|
form.resetFields();
|
||
|
|
form.setFieldsValue({ status: 'active', work_status: 'offline' });
|
||
|
|
setShowModal(true);
|
||
|
|
};
|
||
|
|
|
||
|
|
const openEditModal = (record: any) => {
|
||
|
|
setEditing(record);
|
||
|
|
form.setFieldsValue(record);
|
||
|
|
setShowModal(true);
|
||
|
|
};
|
||
|
|
|
||
|
|
const handleSubmit = async (values: any) => {
|
||
|
|
try {
|
||
|
|
if (editing) {
|
||
|
|
await updateChargingPile(editing.id, values);
|
||
|
|
message.success('充电桩更新成功');
|
||
|
|
} else {
|
||
|
|
await createChargingPile(values);
|
||
|
|
message.success('充电桩创建成功');
|
||
|
|
}
|
||
|
|
setShowModal(false);
|
||
|
|
form.resetFields();
|
||
|
|
loadData();
|
||
|
|
} catch (e: any) {
|
||
|
|
message.error(e?.detail || '操作失败');
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
const handleDelete = async (id: number) => {
|
||
|
|
try {
|
||
|
|
await deleteChargingPile(id);
|
||
|
|
message.success('已停用');
|
||
|
|
loadData();
|
||
|
|
} catch { message.error('操作失败'); }
|
||
|
|
};
|
||
|
|
|
||
|
|
const columns = [
|
||
|
|
{ title: '终端编码', dataIndex: 'encoding', width: 140 },
|
||
|
|
{ title: '名称', dataIndex: 'name', width: 150, ellipsis: true },
|
||
|
|
{ title: '所属充电站', dataIndex: 'station_id', width: 150, render: (id: number) => {
|
||
|
|
const s = stations.find((st: any) => st.id === id);
|
||
|
|
return s ? s.name : id;
|
||
|
|
}},
|
||
|
|
{ title: '类型', dataIndex: 'type', width: 100, render: (v: string) => typeOptions.find(o => o.value === v)?.label || v || '-' },
|
||
|
|
{ title: '额定功率(kW)', dataIndex: 'rated_power_kw', width: 120, render: (v: number) => v != null ? v : '-' },
|
||
|
|
{ title: '品牌', dataIndex: 'brand', width: 100 },
|
||
|
|
{ title: '型号', dataIndex: 'model', width: 100 },
|
||
|
|
{ title: '接口类型', dataIndex: 'connector_type', width: 100 },
|
||
|
|
{ title: '工作状态', dataIndex: 'work_status', width: 100, render: (s: string) => {
|
||
|
|
const st = workStatusMap[s] || { color: 'default', text: s || '-' };
|
||
|
|
return <Tag color={st.color}>{st.text}</Tag>;
|
||
|
|
}},
|
||
|
|
{ title: '状态', dataIndex: 'status', width: 80, render: (s: string) => (
|
||
|
|
<Tag color={s === 'active' ? 'green' : 'default'}>{s === 'active' ? '启用' : '停用'}</Tag>
|
||
|
|
)},
|
||
|
|
{ title: '操作', key: 'action', width: 150, fixed: 'right' as const, render: (_: any, record: any) => (
|
||
|
|
<Space>
|
||
|
|
<Button size="small" type="link" icon={<EditOutlined />} onClick={() => openEditModal(record)}>编辑</Button>
|
||
|
|
<Button size="small" type="link" danger icon={<DeleteOutlined />} onClick={() => handleDelete(record.id)}>停用</Button>
|
||
|
|
</Space>
|
||
|
|
)},
|
||
|
|
];
|
||
|
|
|
||
|
|
return (
|
||
|
|
<Card size="small" title="充电桩管理" extra={
|
||
|
|
<Button type="primary" icon={<PlusOutlined />} onClick={openAddModal}>添加充电桩</Button>
|
||
|
|
}>
|
||
|
|
<Space wrap style={{ marginBottom: 16 }}>
|
||
|
|
<Select allowClear placeholder="所属充电站" style={{ width: 180 }}
|
||
|
|
options={stations.map((s: any) => ({ label: s.name, value: s.id }))}
|
||
|
|
onChange={v => handleFilterChange('station_id', v)} />
|
||
|
|
<Select allowClear placeholder="类型" style={{ width: 120 }} options={typeOptions}
|
||
|
|
onChange={v => handleFilterChange('type', v)} />
|
||
|
|
<Select allowClear placeholder="工作状态" style={{ width: 120 }}
|
||
|
|
options={[
|
||
|
|
{ label: '空闲', value: 'idle' }, { label: '充电中', value: 'charging' },
|
||
|
|
{ label: '故障', value: 'fault' }, { label: '离线', value: 'offline' },
|
||
|
|
]}
|
||
|
|
onChange={v => handleFilterChange('work_status', v)} />
|
||
|
|
</Space>
|
||
|
|
|
||
|
|
<Table
|
||
|
|
columns={columns} dataSource={data.items} rowKey="id"
|
||
|
|
loading={loading} size="small" scroll={{ x: 1400 }}
|
||
|
|
pagination={{
|
||
|
|
current: filters.page,
|
||
|
|
pageSize: filters.page_size,
|
||
|
|
total: data.total,
|
||
|
|
showSizeChanger: true,
|
||
|
|
showTotal: (total: number) => `共 ${total} 个充电桩`,
|
||
|
|
onChange: (page, pageSize) => setFilters(prev => ({ ...prev, page, page_size: pageSize })),
|
||
|
|
}}
|
||
|
|
/>
|
||
|
|
|
||
|
|
<Modal
|
||
|
|
title={editing ? '编辑充电桩' : '添加充电桩'}
|
||
|
|
open={showModal}
|
||
|
|
onCancel={() => { setShowModal(false); form.resetFields(); }}
|
||
|
|
onOk={() => form.submit()}
|
||
|
|
okText={editing ? '保存' : '创建'}
|
||
|
|
cancelText="取消"
|
||
|
|
width={640}
|
||
|
|
destroyOnClose
|
||
|
|
>
|
||
|
|
<Form form={form} layout="vertical" onFinish={handleSubmit}>
|
||
|
|
<Form.Item name="station_id" label="所属充电站" rules={[{ required: true, message: '请选择充电站' }]}>
|
||
|
|
<Select placeholder="选择充电站"
|
||
|
|
options={stations.map((s: any) => ({ label: s.name, value: s.id }))} />
|
||
|
|
</Form.Item>
|
||
|
|
<Form.Item name="encoding" label="终端编码" rules={[{ required: true, message: '请输入终端编码' }]}>
|
||
|
|
<Input placeholder="唯一终端编码" />
|
||
|
|
</Form.Item>
|
||
|
|
<Form.Item name="name" label="名称">
|
||
|
|
<Input placeholder="充电桩名称" />
|
||
|
|
</Form.Item>
|
||
|
|
<Form.Item name="type" label="类型">
|
||
|
|
<Select allowClear placeholder="选择类型" options={typeOptions} />
|
||
|
|
</Form.Item>
|
||
|
|
<Form.Item name="brand" label="品牌">
|
||
|
|
<Select allowClear placeholder="选择品牌"
|
||
|
|
options={brands.map((b: any) => ({ label: b.brand_name, value: b.brand_name }))} />
|
||
|
|
</Form.Item>
|
||
|
|
<Form.Item name="model" label="型号">
|
||
|
|
<Input placeholder="设备型号" />
|
||
|
|
</Form.Item>
|
||
|
|
<Form.Item name="rated_power_kw" label="额定功率(kW)">
|
||
|
|
<InputNumber style={{ width: '100%' }} min={0} step={1} />
|
||
|
|
</Form.Item>
|
||
|
|
<Form.Item name="connector_type" label="接口类型">
|
||
|
|
<Select allowClear placeholder="选择接口类型" options={connectorOptions} />
|
||
|
|
</Form.Item>
|
||
|
|
</Form>
|
||
|
|
</Modal>
|
||
|
|
</Card>
|
||
|
|
);
|
||
|
|
}
|