import React, { useState, useEffect, useCallback } from "react";
|
import {
|
useTranslate,
|
useNotify,
|
useRedirect,
|
} from "react-admin";
|
import {
|
Box,
|
Card,
|
Typography,
|
Stack,
|
Button,
|
Alert,
|
FormControl,
|
InputLabel,
|
Select,
|
MenuItem,
|
CircularProgress,
|
} from "@mui/material";
|
import MoveUpIcon from "@mui/icons-material/MoveUp";
|
import RefreshIcon from "@mui/icons-material/Refresh";
|
import request from "@/utils/request";
|
import ConfirmButton from "../../components/ConfirmButton";
|
import { DataGrid } from "@mui/x-data-grid";
|
import { DEFAULT_PAGE_SIZE } from "@/config/setting";
|
|
const EmptyOutboundList = () => {
|
const translate = useTranslate();
|
const notify = useNotify();
|
const redirect = useRedirect();
|
const [staNo, setStaNo] = useState("");
|
const [stations, setStations] = useState([]);
|
const [selectionModel, setSelectionModel] = useState([]);
|
const [batchLoading, setBatchLoading] = useState(false);
|
const [locList, setLocList] = useState([]);
|
const [total, setTotal] = useState(0);
|
const [isLoading, setIsLoading] = useState(true);
|
const [paginationModel, setPaginationModel] = useState({ page: 0, pageSize: DEFAULT_PAGE_SIZE });
|
|
// 库位列表来自 man_loc 表,使用标准 loc/page 接口,筛选 useStatus=D(空板)
|
const loadEmptyLocs = useCallback(async () => {
|
setIsLoading(true);
|
try {
|
const { data } = await request.post("loc/page", {
|
current: paginationModel.page + 1,
|
pageSize: paginationModel.pageSize,
|
orderBy: "id asc",
|
useStatus: "D",
|
});
|
if (data?.code === 200 && data?.data) {
|
const payload = data.data;
|
const records = payload?.records ?? payload?.list ?? [];
|
const totalCount = payload?.total ?? 0;
|
setLocList(Array.isArray(records) ? records : []);
|
setTotal(Number(totalCount) || 0);
|
} else {
|
setLocList([]);
|
setTotal(0);
|
}
|
} catch (e) {
|
notify(e?.message || "加载空板库位失败", { type: "error" });
|
setLocList([]);
|
setTotal(0);
|
} finally {
|
setIsLoading(false);
|
}
|
}, [paginationModel.page, paginationModel.pageSize, notify]);
|
|
useEffect(() => {
|
loadEmptyLocs();
|
}, [loadEmptyLocs]);
|
|
const loadStations = useCallback(async () => {
|
try {
|
const { data } = await request.post("basStation/page", {
|
current: 1,
|
pageSize: 500,
|
});
|
if (data?.code === 200 && data?.data?.records) {
|
setStations(data.data.records);
|
}
|
} catch (e) {
|
// ignore
|
}
|
}, []);
|
|
useEffect(() => {
|
loadStations();
|
}, [loadStations]);
|
|
const handleBatchOutbound = async () => {
|
if (!staNo?.trim()) {
|
notify("请先选择目标站点", { type: "warning" });
|
return;
|
}
|
const selected = (locList || []).filter((row) => selectionModel.includes(row.id));
|
if (selected.length === 0) {
|
notify("请勾选要出库的空板库位", { type: "warning" });
|
return;
|
}
|
setBatchLoading(true);
|
let success = 0;
|
let fail = 0;
|
for (const loc of selected) {
|
const orgLoc = loc.code;
|
if (!orgLoc) {
|
fail++;
|
continue;
|
}
|
try {
|
const { data } = await request.post("wcs/empty/outbound", {
|
orgLoc,
|
staNo: staNo.trim(),
|
});
|
if (data?.code === 200) {
|
success++;
|
} else {
|
fail++;
|
notify(`库位 ${orgLoc}: ${data?.msg || "失败"}`, { type: "warning" });
|
}
|
} catch (e) {
|
fail++;
|
notify(`库位 ${orgLoc}: ${e?.message || "请求失败"}`, { type: "error" });
|
}
|
}
|
setBatchLoading(false);
|
setSelectionModel([]);
|
loadEmptyLocs();
|
if (success > 0) {
|
notify(`批量出库完成:成功 ${success} 个${fail > 0 ? `,失败 ${fail} 个` : ""}`, { type: success && !fail ? "success" : "warning" });
|
redirect("/task");
|
} else if (fail > 0) {
|
notify(`批量出库失败:${fail} 个`, { type: "error" });
|
}
|
};
|
|
const columns = [
|
{ field: "code", headerName: "库位编码", flex: 1, minWidth: 120 },
|
{ field: "barcode", headerName: "容器/托板码", flex: 1, minWidth: 120 },
|
{ field: "useStatus", headerName: "使用状态", width: 100 },
|
{ field: "channel", headerName: "巷道", width: 80 },
|
{ field: "row", headerName: "行", width: 70 },
|
{ field: "col", headerName: "列", width: 70 },
|
{ field: "lev", headerName: "层", width: 70 },
|
];
|
|
return (
|
<Box sx={{ p: 2 }}>
|
<Card sx={{ p: 2, mb: 2 }}>
|
<Typography variant="h6" sx={{ mb: 2 }}>
|
{translate("menu.emptyOutbound")}
|
</Typography>
|
<Alert severity="info" sx={{ mb: 2 }}>
|
勾选需要出库的库位,选择目标站点后点击「出库」
|
</Alert>
|
<Stack direction="row" alignItems="center" spacing={2} sx={{ mb: 2 }}>
|
<FormControl size="small" sx={{ minWidth: 220 }}>
|
<InputLabel>目标站点</InputLabel>
|
<Select
|
value={staNo}
|
label="目标站点"
|
onChange={(e) => setStaNo(e.target.value)}
|
>
|
<MenuItem value="">请选择</MenuItem>
|
{(stations || []).map((s) => (
|
<MenuItem key={s.id} value={s.stationName || ""}>
|
{s.stationName || s.id}
|
</MenuItem>
|
))}
|
</Select>
|
</FormControl>
|
<Button
|
size="small"
|
startIcon={<RefreshIcon />}
|
onClick={loadEmptyLocs}
|
disabled={isLoading}
|
>
|
刷新
|
</Button>
|
<ConfirmButton
|
variant="contained"
|
color="primary"
|
onConfirm={handleBatchOutbound}
|
disabled={batchLoading || selectionModel.length === 0}
|
label="批量出库"
|
startIcon={batchLoading ? <CircularProgress size={16} color="inherit" /> : <MoveUpIcon />}
|
/>
|
{selectionModel.length > 0 && (
|
<Typography variant="body2" color="text.secondary">
|
已选 {selectionModel.length} 个库位
|
</Typography>
|
)}
|
</Stack>
|
</Card>
|
<Card sx={{ p: 0 }}>
|
<DataGrid
|
rows={locList}
|
rowCount={total}
|
columns={columns}
|
loading={isLoading}
|
checkboxSelection
|
disableRowSelectionOnClick
|
pageSizeOptions={[25, 50, 100]}
|
paginationMode="server"
|
paginationModel={paginationModel}
|
onPaginationModelChange={setPaginationModel}
|
onRowSelectionModelChange={(newSelection) => setSelectionModel(newSelection)}
|
rowSelectionModel={selectionModel}
|
getRowId={(row) => row.id}
|
autoHeight
|
sx={{ border: "none", minHeight: 400 }}
|
localeText={{
|
noRowsLabel: "暂无空板库位",
|
}}
|
/>
|
</Card>
|
</Box>
|
);
|
};
|
|
export default EmptyOutboundList;
|