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,161 @@
|
||||
'use client';
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Alert, Button, Card, Col, Empty, Flex, Image as AntImage, Row, Space, Spin, Table, Tabs, Typography } from 'antd';
|
||||
import type { ColumnsType } from 'antd/es/table';
|
||||
import { ArrowLeftOutlined } from '@ant-design/icons';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { Breadcrumb } from '@/components/Breadcrumb';
|
||||
import { StatusBadge } from '@/components/StatusBadge';
|
||||
import { BackendApi } from '@/services/backendApi';
|
||||
import type { InspectionRecord, MachineDetail, MachineImageItem } from '@/types';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
export default function MachineDetailPage({ params }: { params: { serialNumber: string } }) {
|
||||
const router = useRouter();
|
||||
const serialNumber = decodeURIComponent(params.serialNumber);
|
||||
const [machine, setMachine] = useState<MachineDetail | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [errorMessage, setErrorMessage] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
let isMounted = true;
|
||||
|
||||
const loadMachineDetail = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
setErrorMessage('');
|
||||
const data = await BackendApi.getMachineDetail(serialNumber);
|
||||
if (!isMounted) return;
|
||||
setMachine(data);
|
||||
} catch (error) {
|
||||
if (!isMounted) return;
|
||||
setMachine(null);
|
||||
setErrorMessage(error instanceof Error ? error.message : '机器详情加载失败,请稍后重试');
|
||||
} finally {
|
||||
if (isMounted) {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
loadMachineDetail();
|
||||
|
||||
return () => {
|
||||
isMounted = false;
|
||||
};
|
||||
}, [serialNumber]);
|
||||
|
||||
const renderImageGroup = (images: MachineImageItem[]) => {
|
||||
if (!images.length) return <Empty description="暂无图片" />;
|
||||
|
||||
return (
|
||||
<AntImage.PreviewGroup>
|
||||
<Space size={[16, 16]} wrap>
|
||||
{images.map((image) => (
|
||||
<Flex key={image.id} vertical style={{ position: 'relative', width: 120, gap: 4 }}>
|
||||
<div style={{ width: '100%', aspectRatio: '4/3', overflow: 'hidden', borderRadius: 8, background: '#f0f0f0' }}>
|
||||
<AntImage
|
||||
src={image.url}
|
||||
alt={image.name}
|
||||
width="100%"
|
||||
height="100%"
|
||||
style={{ objectFit: 'cover' }}
|
||||
preview={{ src: image.url }}
|
||||
/>
|
||||
</div>
|
||||
<Text style={{ fontSize: 12, textAlign: 'center' }}>{image.name}</Text>
|
||||
<Text type="secondary" style={{ fontSize: 11, textAlign: 'center' }}>{image.createdAt}</Text>
|
||||
</Flex>
|
||||
))}
|
||||
</Space>
|
||||
</AntImage.PreviewGroup>
|
||||
);
|
||||
};
|
||||
|
||||
const recordColumns: ColumnsType<InspectionRecord> = [
|
||||
{ title: '查验时间', dataIndex: 'time', key: 'time' },
|
||||
{ title: '操作人', dataIndex: 'operator', key: 'operator' },
|
||||
{ title: '结果', dataIndex: 'result', key: 'result' },
|
||||
{ title: '备注', dataIndex: 'remark', key: 'remark' },
|
||||
];
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<Flex vertical align="center" justify="center" style={{ padding: 48 }}>
|
||||
<Spin tip="正在加载机器详情..." />
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
if (!machine) {
|
||||
return (
|
||||
<Flex vertical align="center" style={{ padding: 48 }}>
|
||||
{errorMessage && (
|
||||
<Alert type="error" message={errorMessage} showIcon style={{ maxWidth: 480, marginBottom: 16 }} />
|
||||
)}
|
||||
<Empty description="暂无机器详情" />
|
||||
<Button type="primary" onClick={() => router.push('/machines')} style={{ marginTop: 16 }}>
|
||||
返回机器查询
|
||||
</Button>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
const imageTabs = [
|
||||
{ key: 'incoming', label: '来料检验单', children: renderImageGroup(machine.images.incomingInspection) },
|
||||
{ key: 'startup', label: '开机测试样张', children: renderImageGroup(machine.images.startupTestSample) },
|
||||
{ key: 'production', label: '生产加工单', children: renderImageGroup(machine.images.productionOrder) },
|
||||
{ key: 'robot', label: '机器人查验拍照', children: renderImageGroup(machine.images.robotInspection) },
|
||||
];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Flex justify="space-between" align="center" style={{ marginBottom: 16 }}>
|
||||
<Breadcrumb />
|
||||
<Button icon={<ArrowLeftOutlined />} onClick={() => router.back()}>返回查询</Button>
|
||||
</Flex>
|
||||
|
||||
{errorMessage && (
|
||||
<Alert type="warning" message={errorMessage} showIcon style={{ marginBottom: 16 }} />
|
||||
)}
|
||||
|
||||
<Card title="机器基本信息" style={{ marginBottom: 24 }}>
|
||||
<Row gutter={[24, 16]}>
|
||||
<Col span={8}>
|
||||
<Text type="secondary">序列号:</Text> <Text strong>{machine.serialNumber}</Text>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Text type="secondary">机器型号:</Text> <Text strong>{machine.modelName}</Text>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Text type="secondary">所属报关单:</Text> <Button type="link" disabled={machine.customsId === '-'}>{machine.customsId}</Button>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Text type="secondary">当前状态:</Text> <StatusBadge status={machine.status} />
|
||||
</Col>
|
||||
{Object.entries(machine.specs).map(([key, value]) => (
|
||||
<Col span={8} key={key}>
|
||||
<Text type="secondary">{key}:</Text> <Text>{value}</Text>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
</Card>
|
||||
|
||||
<Card title="图片资料" style={{ marginBottom: 24 }}>
|
||||
<Tabs items={imageTabs} />
|
||||
</Card>
|
||||
|
||||
<Card title="查验记录">
|
||||
<Table
|
||||
dataSource={machine.inspectionRecords}
|
||||
rowKey="id"
|
||||
pagination={false}
|
||||
columns={recordColumns}
|
||||
locale={{ emptyText: <Empty description="暂无查验记录" /> }}
|
||||
/>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user