| New file |
| | |
| | | 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; |