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