Files
smart-inspection/public-frontend/src/app/machines/[serialNumber]/page.tsx
T
FaulknerWu 1429442dbd 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>
2026-06-22 10:18:20 +08:00

162 lines
5.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'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>
);
}