Add customs tablet frontend prototype

This commit is contained in:
2026-06-20 01:25:07 +08:00
parent 87060e30d4
commit f10ef75852
25 changed files with 8703 additions and 3 deletions
@@ -0,0 +1,167 @@
'use client';
import React, { useEffect, useState } from 'react';
import { Alert, Card, Row, Col, Typography, Space, Button, Tabs, Table, Image as AntImage, Empty, Spin, Flex } from 'antd';
import { ArrowLeftOutlined } from '@ant-design/icons';
import { useRouter } from 'next/navigation';
import { Breadcrumb } from '../../../components/Breadcrumb';
import { MockApi } from '../../../services/mockApi';
import { MachineDetail, ImageItem } from '../../../types';
import { StatusBadge } from '../../../components/StatusBadge';
const { Text } = Typography;
export default function MachineDetailPage({ params }: { params: { serialNumber: string } }) {
const router = useRouter();
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 MockApi.getMachineDetail(params.serialNumber);
if (!isMounted) return;
setMachine(data);
} catch {
if (!isMounted) return;
setMachine(null);
setErrorMessage('机器详情加载失败,请稍后重试');
} finally {
if (isMounted) {
setLoading(false);
}
}
};
loadMachineDetail();
return () => {
isMounted = false;
};
}, [params.serialNumber]);
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 renderImageGroup = (images: ImageItem[]) => {
if (!images || images.length === 0) return <Empty description="暂无图片" />;
return (
<AntImage.PreviewGroup>
<Space size={[16, 16]} wrap>
{images.map((img) => (
<Flex
key={img.id}
vertical
style={{
position: 'relative',
width: 120,
gap: 4
}}
>
<div style={{ width: '100%', aspectRatio: '4/3', overflow: 'hidden', borderRadius: 8, background: '#f0f0f0' }}>
<AntImage
src={img.url}
alt={img.name}
width="100%"
height="100%"
style={{ objectFit: 'cover' }}
preview={{
src: img.url,
}}
/>
</div>
<Text style={{ fontSize: 12, textAlign: 'center' }}>{img.name}</Text>
<Text type="secondary" style={{ fontSize: 11, textAlign: 'center' }}>{img.createdAt}</Text>
</Flex>
))}
</Space>
</AntImage.PreviewGroup>
);
};
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>
<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">{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}
locale={{ emptyText: <Empty description="暂无查验记录" /> }}
>
<Table.Column title="查验时间" dataIndex="time" />
<Table.Column title="操作人" dataIndex="operator" />
<Table.Column title="结果" dataIndex="result" />
<Table.Column title="备注" dataIndex="remark" />
</Table>
</Card>
</div>
);
}