import React, { useEffect, useState, useRef } from 'react'; import { Card, Button, Space, Select, message, Modal, Form, Input, InputNumber, Drawer } from 'antd'; import { Stage, Layer, Rect, Line, Circle, Text as KonvaText } from 'react-konva'; import axios from 'axios'; interface ROI { id: number; roi_id: string; name: string; type: string; points: number[][]; rule: string; direction: string | null; enabled: boolean; threshold_sec: number; confirm_sec: number; return_sec: number; } interface Camera { id: number; name: string; } const ROIEditor: React.FC = () => { const [cameras, setCameras] = useState([]); const [selectedCamera, setSelectedCamera] = useState(null); const [rois, setRois] = useState([]); const [snapshot, setSnapshot] = useState(''); const [loading, setLoading] = useState(false); const [imageDim, setImageDim] = useState({ width: 800, height: 600 }); const [selectedROI, setSelectedROI] = useState(null); const [drawerVisible, setDrawerVisible] = useState(false); const [form] = Form.useForm(); const stageRef = useRef(null); const fetchCameras = async () => { try { const res = await axios.get('/api/cameras?enabled_only=true'); setCameras(res.data); if (res.data.length > 0 && !selectedCamera) { setSelectedCamera(res.data[0].id); } } catch (err) { message.error('获取摄像头列表失败'); } }; useEffect(() => { fetchCameras(); }, []); useEffect(() => { if (selectedCamera) { fetchSnapshot(); fetchROIs(); } }, [selectedCamera]); const fetchSnapshot = async () => { if (!selectedCamera) return; try { const res = await axios.get(`/api/camera/${selectedCamera}/snapshot/base64`); setSnapshot(res.data.image); const img = new Image(); img.onload = () => { const maxWidth = 800; const maxHeight = 600; const scale = Math.min(maxWidth / img.width, maxHeight / img.height); setImageDim({ width: img.width * scale, height: img.height * scale }); }; img.src = `data:image/jpeg;base64,${res.data.image}`; } catch (err) { message.error('获取截图失败'); } }; const fetchROIs = async () => { if (!selectedCamera) return; try { const res = await axios.get(`/api/camera/${selectedCamera}/rois`); setRois(res.data); } catch (err) { message.error('获取ROI列表失败'); } }; const handleSaveROI = async (values: any) => { if (!selectedCamera || !selectedROI) return; try { await axios.put(`/api/camera/${selectedCamera}/roi/${selectedROI.id}`, values); message.success('保存成功'); setDrawerVisible(false); fetchROIs(); } catch (err) { message.error('保存失败'); } }; const handleAddROI = async () => { if (!selectedCamera) return; const roi_id = `roi_${Date.now()}`; try { await axios.post(`/api/camera/${selectedCamera}/roi`, { roi_id, name: '新区域', roi_type: 'polygon', points: [[100, 100], [300, 100], [300, 300], [100, 300]], rule_type: 'leave_post', threshold_sec: 360, confirm_sec: 30, return_sec: 5, }); message.success('添加成功'); fetchROIs(); } catch (err) { message.error('添加失败'); } }; const handleDeleteROI = async (roiId: number) => { if (!selectedCamera) return; try { await axios.delete(`/api/camera/${selectedCamera}/roi/${roiId}`); message.success('删除成功'); fetchROIs(); } catch (err) { message.error('删除失败'); } }; const getROIStrokeColor = (rule: string) => { return rule === 'intrusion' ? '#ff4d4f' : '#faad14'; }; const renderROI = (roi: ROI) => { const points = roi.points.flat(); const color = getROIStrokeColor(roi.rule); if (roi.type === 'polygon') { return ( { setSelectedROI(roi); form.setFieldsValue(roi); setDrawerVisible(true); }} /> ); } else if (roi.type === 'line') { return ( { setSelectedROI(roi); form.setFieldsValue(roi); setDrawerVisible(true); }} /> ); } return null; }; return (
); }; export default ROIEditor;