From 460b1c8473e08a3449bff35d7a34cbf16ad1c79a Mon Sep 17 00:00:00 2001
From: chen.lin <1442464845@qq.com>
Date: 星期五, 06 三月 2026 08:15:04 +0800
Subject: [PATCH] 拣货过程中的出库库存匹配+空板

---
 rsf-admin/src/page/work/emptyOutbound/index.jsx             |    7 +
 rsf-admin/src/page/work/emptyOutbound/EmptyOutboundList.jsx |  221 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 228 insertions(+), 0 deletions(-)

diff --git a/rsf-admin/src/page/work/emptyOutbound/EmptyOutboundList.jsx b/rsf-admin/src/page/work/emptyOutbound/EmptyOutboundList.jsx
new file mode 100644
index 0000000..a9e05de
--- /dev/null
+++ b/rsf-admin/src/page/work/emptyOutbound/EmptyOutboundList.jsx
@@ -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;
diff --git a/rsf-admin/src/page/work/emptyOutbound/index.jsx b/rsf-admin/src/page/work/emptyOutbound/index.jsx
new file mode 100644
index 0000000..35c9781
--- /dev/null
+++ b/rsf-admin/src/page/work/emptyOutbound/index.jsx
@@ -0,0 +1,7 @@
+import React from "react";
+import EmptyOutboundList from "./EmptyOutboundList";
+
+export default {
+    list: EmptyOutboundList,
+    options: { label: "menu.emptyOutbound" },
+};

--
Gitblit v1.9.1