Rename customs-tablet-frontend to public-frontend and add new features
- Rename customs-tablet-frontend/ to public-frontend/ for broader scope - Add new pages: customs, inspection with camera integration - Add new services: apiClient.ts, backendApi.ts, normalizers.ts - Add CameraFrame component for real-time video streaming - Add scan_fixer module with clock_publisher and timestamp fix utilities - Update startup scripts to support new frontend structure - Update arm_server configuration and service files Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
'use client';
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { Alert, Badge, Breadcrumb as AntdBreadcrumb, Button, Card, Col, Empty, Flex, Row, Space, Spin, Typography, message } from 'antd';
|
||||
import { ArrowLeftOutlined, CameraOutlined, FullscreenOutlined, HomeOutlined, ReloadOutlined } from '@ant-design/icons';
|
||||
import { Breadcrumb } from '@/components/Breadcrumb';
|
||||
import { CameraFrame } from '@/components/CameraFrame';
|
||||
import { BackendApi } from '@/services/backendApi';
|
||||
import type { CameraInfo } from '@/types';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
export default function VideoPage() {
|
||||
const [cameras, setCameras] = useState<CameraInfo[]>([]);
|
||||
const [fullscreenCamera, setFullscreenCamera] = useState<CameraInfo | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [errorMessage, setErrorMessage] = useState('');
|
||||
const [messageApi, contextHolder] = message.useMessage();
|
||||
|
||||
const loadCameras = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
setErrorMessage('');
|
||||
const cameraList = await BackendApi.getCameras();
|
||||
setCameras(cameraList);
|
||||
} catch (error) {
|
||||
setErrorMessage(error instanceof Error ? error.message : '摄像头列表加载失败,请稍后重试');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
loadCameras();
|
||||
}, []);
|
||||
|
||||
if (fullscreenCamera) {
|
||||
return (
|
||||
<div>
|
||||
{contextHolder}
|
||||
<Flex justify="space-between" align="center" style={{ marginBottom: 16 }}>
|
||||
<Space align="center" size="middle">
|
||||
<Button icon={<ArrowLeftOutlined />} onClick={() => setFullscreenCamera(null)}>返回</Button>
|
||||
<AntdBreadcrumb
|
||||
items={[
|
||||
{ title: <Link href="/"><HomeOutlined /> 首页</Link> },
|
||||
{ title: <a href="#" onClick={(event) => { event.preventDefault(); setFullscreenCamera(null); }}>视频监控</a> },
|
||||
{ title: fullscreenCamera.name },
|
||||
]}
|
||||
/>
|
||||
</Space>
|
||||
<Button type="primary" icon={<CameraOutlined />} onClick={() => messageApi.info('截图接口暂未提供')}>截图</Button>
|
||||
</Flex>
|
||||
|
||||
<Flex justify="center" align="center" style={{ height: 'calc(100vh - 180px)', marginBottom: 16 }}>
|
||||
<div style={{ height: '100%', maxWidth: '100%', aspectRatio: '16 / 9', boxShadow: '0 8px 24px rgba(0,0,0,0.1)' }}>
|
||||
<CameraFrame camera={fullscreenCamera} height="100%" />
|
||||
</div>
|
||||
</Flex>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{contextHolder}
|
||||
<Flex justify="space-between" align="center" style={{ marginBottom: 24 }}>
|
||||
<Breadcrumb />
|
||||
<Space>
|
||||
<Button icon={<ReloadOutlined />} onClick={loadCameras}>刷新</Button>
|
||||
<Button icon={<FullscreenOutlined />} onClick={() => messageApi.info('请选择一个在线画面进入全屏')}>全屏模式</Button>
|
||||
<Button type="primary" icon={<CameraOutlined />} onClick={() => messageApi.info('全部截图接口暂未提供')}>全部截图</Button>
|
||||
</Space>
|
||||
</Flex>
|
||||
|
||||
{errorMessage && (
|
||||
<Alert type="error" message={errorMessage} showIcon style={{ marginBottom: 24 }} />
|
||||
)}
|
||||
|
||||
{loading ? (
|
||||
<Flex vertical align="center" justify="center" style={{ padding: 64 }}>
|
||||
<Spin size="large" tip="正在加载摄像头..." />
|
||||
</Flex>
|
||||
) : cameras.length ? (
|
||||
<Row gutter={[24, 24]}>
|
||||
{cameras.map((camera) => (
|
||||
<Col xs={24} lg={12} key={camera.id}>
|
||||
<Card
|
||||
hoverable={camera.status === 'online'}
|
||||
style={{ overflow: 'hidden', borderRadius: 12, borderColor: camera.status === 'online' ? '#f0f0f0' : '#ffccc7' }}
|
||||
styles={{ body: { padding: 0 } }}
|
||||
onClick={() => camera.status === 'online' && setFullscreenCamera(camera)}
|
||||
>
|
||||
<CameraFrame camera={camera} height={300} />
|
||||
<Flex justify="space-between" align="center" style={{ padding: '12px 20px', background: camera.status === 'online' ? '#ffffff' : '#fff1f0', borderTop: '1px solid #f0f0f0' }}>
|
||||
<Space size="middle">
|
||||
<Badge status={camera.status === 'online' ? 'processing' : 'error'} />
|
||||
<Text strong style={{ fontSize: 15, color: camera.status === 'online' ? 'inherit' : '#cf1322' }}>{camera.name}</Text>
|
||||
{camera.placeholder && <Text type="secondary" style={{ fontSize: 12 }}>占位</Text>}
|
||||
</Space>
|
||||
{camera.status === 'online' && <Button type="link" icon={<FullscreenOutlined />} size="small">全屏观看</Button>}
|
||||
</Flex>
|
||||
</Card>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
) : (
|
||||
<Empty description="暂无摄像头数据" />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user