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.
This commit is contained in:
kae 2026-06-18 19:20:49 +09:00
parent c08a9a4854
commit 7545799fdc
6 changed files with 259 additions and 35 deletions

View File

@ -63,7 +63,9 @@ export default defineConfig({
{ name: '库存查询', path: '/inventory/stocks', component: './Inventory/Stocks' }, { name: '库存查询', path: '/inventory/stocks', component: './Inventory/Stocks' },
{ name: '库存统计', path: '/inventory/stats', component: './Inventory/Stats' }, { name: '库存统计', path: '/inventory/stats', component: './Inventory/Stats' },
{ name: '库存盘点', path: '/inventory/checks', component: './Inventory/Checks' }, { name: '库存盘点', path: '/inventory/checks', component: './Inventory/Checks' },
{ name: '盘点单详情', path: '/inventory/checks/:id', component: './Inventory/Checks/detail', hideInMenu: true },
{ name: '库存调整', path: '/inventory/adjusts', component: './Inventory/Adjusts' }, { name: '库存调整', path: '/inventory/adjusts', component: './Inventory/Adjusts' },
{ name: '调整单详情', path: '/inventory/adjusts/:id', component: './Inventory/Adjusts/detail', hideInMenu: true },
{ name: 'Excel导入', path: '/inventory/import', component: './Inventory/Import' }, { name: 'Excel导入', path: '/inventory/import', component: './Inventory/Import' },
{ name: '库存变动记录', path: '/inventory/logs', component: './Inventory/Logs' }, { name: '库存变动记录', path: '/inventory/logs', component: './Inventory/Logs' },
], ],

View File

@ -0,0 +1,98 @@
import { ArrowLeftOutlined } from '@ant-design/icons';
import { ProTable } from '@ant-design/pro-components';
import { Button, Card, Descriptions, Modal, Space, Tag, Typography, message } from 'antd';
import { useEffect, useState } from 'react';
import { history, useParams } from '@umijs/max';
import { approveStockAdjust, getStockAdjust } from '@/services/api';
import type { StockAdjust } from '@/types/api';
const STATUS_MAP: Record<number, { text: string; color: string }> = {
0: { text: '待审核', color: 'warning' },
1: { text: '已审核', color: 'success' },
2: { text: '已驳回', color: 'error' },
};
const AdjustDetailPage: React.FC = () => {
const { id } = useParams<{ id: string }>();
const [adjust, setAdjust] = useState<StockAdjust | null>(null);
const load = async () => {
if (!id) return;
const res = await getStockAdjust(id);
if (res?.code === 0) setAdjust(res.data as StockAdjust);
};
useEffect(() => { load(); }, [id]);
const handleApprove = (action: number) => {
Modal.confirm({
title: action === 1 ? '确认审核通过?此操作将实际调整库存。' : '确认驳回此调整单?',
okType: action === 2 ? 'danger' : 'primary',
onOk: async () => {
await approveStockAdjust(id!, action);
message.success(action === 1 ? '已审核通过' : '已驳回');
load();
},
});
};
if (!adjust) return null;
const status = STATUS_MAP[adjust.status] ?? STATUS_MAP[0];
return (
<Space direction="vertical" style={{ width: '100%' }} size="large">
<Space>
<Button icon={<ArrowLeftOutlined />} onClick={() => history.back()}></Button>
<Typography.Title level={4} style={{ margin: 0 }}> {adjust.adjustNo}</Typography.Title>
</Space>
<Card>
<Descriptions column={3} bordered size="small">
<Descriptions.Item label="调整单号">{adjust.adjustNo}</Descriptions.Item>
<Descriptions.Item label="调整日期">{adjust.adjustDate}</Descriptions.Item>
<Descriptions.Item label="调整原因">{adjust.adjustReason || '-'}</Descriptions.Item>
<Descriptions.Item label="操作人">{adjust.operator || '-'}</Descriptions.Item>
<Descriptions.Item label="审核人">{adjust.approver || '-'}</Descriptions.Item>
<Descriptions.Item label="状态">
<Tag color={status.color}>{status.text}</Tag>
</Descriptions.Item>
{adjust.remark && <Descriptions.Item label="备注" span={3}>{adjust.remark}</Descriptions.Item>}
</Descriptions>
{adjust.status === 0 && (
<Space style={{ marginTop: 16 }}>
<Button type="primary" onClick={() => handleApprove(1)}></Button>
<Button danger onClick={() => handleApprove(2)}></Button>
</Space>
)}
</Card>
<Card title="调整明细">
<ProTable
headerTitle={false}
rowKey="detailId"
search={false}
pagination={false}
dataSource={adjust.details || []}
columns={[
{ title: '产品', dataIndex: 'productName' },
{ title: '调整前数量(米)', dataIndex: 'beforeQuantity' },
{
title: '调整数量(米)',
dataIndex: 'adjustQuantity',
render: (v: string) => {
const n = parseFloat(v);
const color = n > 0 ? '#52c41a' : n < 0 ? '#ff4d4f' : undefined;
return <span style={{ color }}>{n > 0 ? `+${v}` : v}</span>;
},
},
{ title: '调整后数量(米)', dataIndex: 'afterQuantity' },
{ title: '备注', dataIndex: 'remark', render: (v: string) => v || '-' },
]}
/>
</Card>
</Space>
);
};
export default AdjustDetailPage;

View File

@ -2,6 +2,7 @@ import { DeleteOutlined, PlusOutlined } from '@ant-design/icons';
import { ActionType, ProColumns, ProTable } from '@ant-design/pro-components'; import { ActionType, ProColumns, ProTable } from '@ant-design/pro-components';
import { Button, Col, Form, Input, Modal, Row, Select, Table, Tag, message } from 'antd'; import { Button, Col, Form, Input, Modal, Row, Select, Table, Tag, message } from 'antd';
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
import { Link } from '@umijs/max';
import { createStockAdjust, listProducts, listStockAdjusts } from '@/services/api'; import { createStockAdjust, listProducts, listStockAdjusts } from '@/services/api';
import type { Product, StockAdjust } from '@/types/api'; import type { Product, StockAdjust } from '@/types/api';
@ -14,25 +15,24 @@ const STATUS_MAP: Record<number, { text: string; color: string }> = {
interface DetailRow { key: number; productId?: string; adjustQuantity?: string } interface DetailRow { key: number; productId?: string; adjustQuantity?: string }
let rowKey = 0; let rowKey = 0;
const columns: ProColumns<StockAdjust>[] = [
{ title: '调整单号', dataIndex: 'adjustNo' },
{ title: '调整日期', dataIndex: 'adjustDate', valueType: 'date', search: false },
{ title: '调整原因', dataIndex: 'adjustReason', search: false },
{ title: '操作人', dataIndex: 'operator', 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: () => [<a key="view"></a>] },
];
const AdjustsPage: React.FC = () => { const AdjustsPage: React.FC = () => {
const columns: ProColumns<StockAdjust>[] = [
{ title: '调整单号', dataIndex: 'adjustNo' },
{ title: '调整日期', dataIndex: 'adjustDate', valueType: 'date', search: false },
{ title: '调整原因', dataIndex: 'adjustReason', search: false },
{ title: '操作人', dataIndex: 'operator', 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/adjusts/${record.adjustId}`}></Link>] },
];
const actionRef = useRef<ActionType>(); const actionRef = useRef<ActionType>();
const [form] = Form.useForm(); const [form] = Form.useForm();
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);

View File

@ -0,0 +1,94 @@
import { ArrowLeftOutlined } from '@ant-design/icons';
import { ProTable } from '@ant-design/pro-components';
import { Button, Card, Descriptions, Modal, Space, Tag, Typography, message } from 'antd';
import { useEffect, useState } from 'react';
import { history, useParams } from '@umijs/max';
import { confirmStockCheck, getStockCheck } from '@/services/api';
import type { 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' },
};
const CheckDetailPage: React.FC = () => {
const { id } = useParams<{ id: string }>();
const [check, setCheck] = useState<StockCheck | null>(null);
const load = async () => {
if (!id) return;
const res = await getStockCheck(id);
if (res?.code === 0) setCheck(res.data as StockCheck);
};
useEffect(() => { load(); }, [id]);
const handleConfirm = () => {
Modal.confirm({
title: '确认开始盘点?',
onOk: async () => {
await confirmStockCheck(id!);
message.success('已确认');
load();
},
});
};
if (!check) return null;
const status = STATUS_MAP[check.status] ?? STATUS_MAP[0];
return (
<Space direction="vertical" style={{ width: '100%' }} size="large">
<Space>
<Button icon={<ArrowLeftOutlined />} onClick={() => history.back()}></Button>
<Typography.Title level={4} style={{ margin: 0 }}> {check.checkNo}</Typography.Title>
</Space>
<Card>
<Descriptions column={3} bordered size="small">
<Descriptions.Item label="盘点单号">{check.checkNo}</Descriptions.Item>
<Descriptions.Item label="盘点日期">{check.checkDate}</Descriptions.Item>
<Descriptions.Item label="盘点人">{check.checker || '-'}</Descriptions.Item>
<Descriptions.Item label="状态">
<Tag color={status.color}>{status.text}</Tag>
</Descriptions.Item>
<Descriptions.Item label="创建时间">{check.createdAt}</Descriptions.Item>
{check.remark && <Descriptions.Item label="备注" span={3}>{check.remark}</Descriptions.Item>}
</Descriptions>
{check.status === 0 && (
<Button type="primary" style={{ marginTop: 16 }} onClick={handleConfirm}></Button>
)}
</Card>
<Card title="盘点明细">
<ProTable
headerTitle={false}
rowKey="detailId"
search={false}
pagination={false}
dataSource={check.details || []}
columns={[
{ title: '产品', dataIndex: 'productName' },
{ title: '账面数量(米)', dataIndex: 'systemQuantity' },
{ title: '实盘数量(米)', dataIndex: 'actualQuantity' },
{
title: '差异数量(米)',
dataIndex: 'diffQuantity',
render: (v: string) => {
const n = parseFloat(v);
const color = n > 0 ? '#52c41a' : n < 0 ? '#ff4d4f' : undefined;
return <span style={{ color }}>{v}</span>;
},
},
{ title: '差异金额', dataIndex: 'diffAmount', render: (v: string) => `¥ ${v}` },
{ title: '备注', dataIndex: 'remark', render: (v: string) => v || '-' },
]}
/>
</Card>
</Space>
);
};
export default CheckDetailPage;

View File

@ -2,6 +2,7 @@ import { DeleteOutlined, PlusOutlined } from '@ant-design/icons';
import { ActionType, ProColumns, ProTable } from '@ant-design/pro-components'; import { ActionType, ProColumns, ProTable } from '@ant-design/pro-components';
import { Button, Col, Form, Input, Modal, Row, Select, Table, Tag, message } from 'antd'; import { Button, Col, Form, Input, Modal, Row, Select, Table, Tag, message } from 'antd';
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
import { Link } from '@umijs/max';
import { createStockCheck, listProducts, listStockChecks } from '@/services/api'; import { createStockCheck, listProducts, listStockChecks } from '@/services/api';
import type { Product, StockCheck } from '@/types/api'; import type { Product, StockCheck } from '@/types/api';
@ -14,24 +15,23 @@ const STATUS_MAP: Record<number, { text: string; color: string }> = {
interface DetailRow { key: number; productId?: string; actualQuantity?: string } interface DetailRow { key: number; productId?: string; actualQuantity?: string }
let rowKey = 0; let rowKey = 0;
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: () => [<a key="view"></a>] },
];
const ChecksPage: React.FC = () => { 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 actionRef = useRef<ActionType>();
const [form] = Form.useForm(); const [form] = Form.useForm();
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);

View File

@ -144,13 +144,39 @@ export interface StockGroup {
quantity: number; quantity: number;
} }
export interface StockCheckDetail {
detailId: string;
checkId: string;
productId: string;
productName: string;
systemQuantity: string;
actualQuantity: string;
diffQuantity: string;
diffAmount: string;
remark?: string;
}
export interface StockCheck { export interface StockCheck {
checkId: string; checkId: string;
checkNo: string; checkNo: string;
checkDate: string; checkDate: string;
checker: string; checker: string;
status: number; status: number;
remark?: string;
createdAt: string; createdAt: string;
updatedAt?: string;
details: StockCheckDetail[];
}
export interface StockAdjustDetail {
detailId: string;
adjustId: string;
productId: string;
productName: string;
beforeQuantity: string;
adjustQuantity: string;
afterQuantity: string;
remark?: string;
} }
export interface StockAdjust { export interface StockAdjust {
@ -159,8 +185,12 @@ export interface StockAdjust {
adjustDate: string; adjustDate: string;
adjustReason: string; adjustReason: string;
operator: string; operator: string;
approver?: string;
status: number; status: number;
remark?: string;
createdAt: string; createdAt: string;
updatedAt?: string;
details: StockAdjustDetail[];
} }
// ── System ──────────────────────────────────────── // ── System ────────────────────────────────────────