chen.lin
昨天 460b1c8473e08a3449bff35d7a34cbf16ad1c79a
拣货过程中的出库库存匹配+空板
2个文件已添加
228 ■■■■■ 已修改文件
rsf-admin/src/page/work/emptyOutbound/EmptyOutboundList.jsx 221 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/work/emptyOutbound/index.jsx 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/work/emptyOutbound/EmptyOutboundList.jsx
New file
@@ -0,0 +1,221 @@
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;
rsf-admin/src/page/work/emptyOutbound/index.jsx
New file
@@ -0,0 +1,7 @@
import React from "react";
import EmptyOutboundList from "./EmptyOutboundList";
export default {
    list: EmptyOutboundList,
    options: { label: "menu.emptyOutbound" },
};