kae 7545799fdc feat: add detail pages for stock checks and adjusts
Both list pages had 查看 links with no onClick. Added detail pages showing
header info and line items, with action buttons (确认开始盘点 / 审核通过+驳回).

Also: extended StockCheck/StockAdjust types to include details and extra
fields; added /inventory/checks/:id and /inventory/adjusts/:id routes;
moved columns definitions inside components (module-level columns with
history.push were silently ignored by the browser due to MFSU caching);
switched to Link component for SPA navigation.
2026-06-18 19:20:49 +09:00

182 lines
6.4 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.

import { DeleteOutlined, PlusOutlined } from '@ant-design/icons';
import { ActionType, ProColumns, ProTable } from '@ant-design/pro-components';
import { Button, Col, Form, Input, Modal, Row, Select, Table, Tag, message } from 'antd';
import { useEffect, useRef, useState } from 'react';
import { Link } from '@umijs/max';
import { createStockCheck, listProducts, listStockChecks } from '@/services/api';
import type { Product, StockCheck } from '@/types/api';
const STATUS_MAP: Record<number, { text: string; color: string }> = {
0: { text: '草稿', color: 'default' },
1: { text: '进行中', color: 'processing' },
2: { text: '已完成', color: 'success' },
};
interface DetailRow { key: number; productId?: string; actualQuantity?: string }
let rowKey = 0;
const ChecksPage: React.FC = () => {
const columns: ProColumns<StockCheck>[] = [
{ title: '盘点单号', dataIndex: 'checkNo' },
{ title: '盘点日期', dataIndex: 'checkDate', valueType: 'date', search: false },
{ title: '盘点人', dataIndex: 'checker', search: false },
{
title: '状态',
dataIndex: 'status',
search: false,
render: (_, record) => {
const s = STATUS_MAP[record.status] ?? STATUS_MAP[0];
return <Tag color={s.color}>{s.text}</Tag>;
},
},
{ title: '创建时间', dataIndex: 'createdAt', valueType: 'dateTime', search: false },
{ title: '操作', valueType: 'option', render: (_, record) => [<Link key="view" to={`/inventory/checks/${record.checkId}`}></Link>] },
];
const actionRef = useRef<ActionType>();
const [form] = Form.useForm();
const [open, setOpen] = useState(false);
const [submitting, setSubmitting] = useState(false);
const [products, setProducts] = useState<Product[]>([]);
const [details, setDetails] = useState<DetailRow[]>([]);
useEffect(() => {
listProducts({ page: 1, pageSize: 500 }).then((res) => {
if (res?.code === 0) setProducts(res.data?.list || []);
});
}, []);
const handleOpen = () => {
setDetails([]);
setOpen(true);
};
const handleSubmit = async () => {
let values: { checkDate: string; checker?: string; remark?: string };
try {
values = await form.validateFields();
} catch {
return;
}
setSubmitting(true);
try {
const res = await createStockCheck({
...values,
details: details
.filter((r) => r.productId && r.actualQuantity)
.map((r) => ({ productId: r.productId!, actualQuantity: r.actualQuantity! })),
});
if (res?.code !== 0) { message.error(res?.msg || '创建失败'); return; }
setOpen(false);
actionRef.current?.reload();
message.success('盘点单已创建');
} catch {
message.error('创建失败');
} finally {
setSubmitting(false);
}
};
const addRow = () => setDetails((prev) => [...prev, { key: rowKey++ }]);
const removeRow = (idx: number) => setDetails((prev) => prev.filter((_, i) => i !== idx));
const updateRow = (idx: number, field: keyof DetailRow, value: string) =>
setDetails((prev) => prev.map((r, i) => (i === idx ? { ...r, [field]: value } : r)));
const today = new Date().toISOString().slice(0, 10);
return (
<>
<ProTable<StockCheck>
actionRef={actionRef}
headerTitle="库存盘点"
rowKey="checkId"
columns={columns}
search={false}
request={async (params) => {
const res = await listStockChecks({ page: params.current, pageSize: params.pageSize });
return { data: res?.data?.list || [], total: res?.data?.total || 0, success: res?.code === 0 };
}}
toolBarRender={() => [
<Button key="add" type="primary" icon={<PlusOutlined />} onClick={handleOpen}></Button>,
]}
/>
<Modal
title="创建盘点单"
open={open}
onOk={handleSubmit}
onCancel={() => setOpen(false)}
confirmLoading={submitting}
width={680}
afterOpenChange={(o) => { if (!o) form.resetFields(); }}
>
<Form form={form} layout="vertical" initialValues={{ checkDate: today, checker: '', remark: '' }}>
<Row gutter={16}>
<Col span={12}>
<Form.Item name="checkDate" label="盘点日期" rules={[{ required: true, message: '请选择日期' }]}>
<Input type="date" />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item name="checker" label="盘点人">
<Input placeholder="可选" />
</Form.Item>
</Col>
</Row>
<Form.Item name="remark" label="备注">
<Input.TextArea rows={2} />
</Form.Item>
</Form>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 8 }}>
<span style={{ fontWeight: 500 }}></span>
<Button size="small" icon={<PlusOutlined />} onClick={addRow}></Button>
</div>
<Table
size="small"
rowKey="key"
dataSource={details}
pagination={false}
columns={[
{
title: '产品',
render: (_, __, idx) => (
<Select
style={{ width: '100%' }}
showSearch
placeholder="选择产品"
value={details[idx].productId}
optionFilterProp="label"
options={products.map((p) => ({ value: p.productId, label: `${p.productName}${p.spec ? ' ' + p.spec : ''}${p.color ? ' ' + p.color : ''}` }))}
onChange={(v) => updateRow(idx, 'productId', v)}
/>
),
},
{
title: '实盘数量(米)',
width: 140,
render: (_, __, idx) => (
<Input
type="number"
min={0}
step="0.01"
value={details[idx].actualQuantity}
onChange={(e) => updateRow(idx, 'actualQuantity', e.target.value)}
/>
),
},
{
title: '',
width: 40,
render: (_, __, idx) => (
<Button type="text" danger icon={<DeleteOutlined />} onClick={() => removeRow(idx)} />
),
},
]}
/>
</Modal>
</>
);
};
export default ChecksPage;