chen.lin
5 天以前 de18a5db43131dde1ec8b7ac9327f1802f2120a9
批次出库过滤忽略
3个文件已修改
389 ■■■■ 已修改文件
rsf-admin/src/layout/TabsBar.jsx 380 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/java/com/vincent/rsf/openApi/config/ApiSecurityConfig.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/OutStockServiceImpl.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/layout/TabsBar.jsx
@@ -7,32 +7,27 @@
import SettingsIcon from '@mui/icons-material/Settings';
import ClearAllIcon from '@mui/icons-material/ClearAll';
// 固定的标签页配置 (不可关闭)
// 固定标签页配置(不可关闭)
const FIXED_TABS = [
    { path: '/dashboard', name: 'menu.dashboard', closable: false }
];
// 特殊页面配置
const SPECIAL_PAGES = {
    '/settings': { name: 'menu.settings', closable: true },
    '/dashboard': { name: 'menu.dashboard', closable: false }
};
// 忽略的路径模式 (登录页、根路径等)
const IGNORED_PATHS = ['/', '/login'];
// 从localStorage获取已保存的标签页
const getSavedTabs = () => {
    try {
        const saved = localStorage.getItem('openTabs');
        if (saved) {
            const parsed = JSON.parse(saved);
            // 确保 dashboard 始终存在且是第一个
            const hasDashboard = parsed.some(tab => tab.path === '/dashboard');
            if (!hasDashboard) {
                return [...FIXED_TABS, ...parsed.filter(tab => tab.path !== '/dashboard')];
            }
            // 确保 dashboard 在第一位
            const dashboard = parsed.find(tab => tab.path === '/dashboard');
            const others = parsed.filter(tab => tab.path !== '/dashboard');
            return [{ ...dashboard, closable: false }, ...others];
@@ -43,7 +38,6 @@
    }
};
// 保存标签页到localStorage
const saveTabs = (tabs) => {
    try {
        localStorage.setItem('openTabs', JSON.stringify(tabs));
@@ -52,32 +46,25 @@
    }
};
// 规范化路径 - 处理带ID的详情/编辑页面
const normalizePath = (path) => {
    if (!path) return path;
    // 匹配模式: /resource/123/show 或 /resource/123/edit 或 /resource/123
    // 将其规范化为基础路径 /resource
    const patterns = [
        /^(\/[^/]+)\/\d+\/(show|edit)$/,  // /task/123/show -> /task
        /^(\/[^/]+)\/\d+$/,                 // /task/123 -> /task
        /^(\/[^/]+)\/create$/,              // /task/create -> /task
        /^(\/[^/]+)\/\d+\/(show|edit)$/,
        /^(\/[^/]+)\/\d+$/,
        /^(\/[^/]+)\/create$/,
    ];
    for (const pattern of patterns) {
        const match = path.match(pattern);
        if (match) {
            return match[1];
        }
        if (match) return match[1];
    }
    return path;
};
// 判断两个路径是否属于同一资源
const isSameResource = (path1, path2) => {
    return normalizePath(path1) === normalizePath(path2);
};
const LONG_PRESS_MS = 300;
const TabsBar = () => {
    const location = useLocation();
@@ -86,16 +73,42 @@
    const { permissions } = usePermissions();
    const [tabs, setTabs] = useState(getSavedTabs);
    const pendingNavigationRef = useRef(null);
    const tabsBarRef = useRef(null);
    const [contextMenu, setContextMenu] = useState(null);
    const [contextMenuTab, setContextMenuTab] = useState(null);
    const contextMenuOpenRef = useRef(false);
    contextMenuOpenRef.current = contextMenu !== null;
    // 翻译辅助函数,如果翻译不存在则返回默认值
    const [draggingIndex, setDraggingIndex] = useState(null);
    const [dropIndicatorIndex, setDropIndicatorIndex] = useState(null);
    const longPressTimerRef = useRef(null);
    const longPressIndexRef = useRef(null);
    const justFinishedDragRef = useRef(false);
    // 在标签页右键时阻止浏览器默认菜单
    useEffect(() => {
        const onDocContextMenu = (e) => {
            const inTabsBarByTarget = tabsBarRef.current && tabsBarRef.current.contains(e.target);
            let inTabsBarByPos = false;
            if (tabsBarRef.current) {
                const rect = tabsBarRef.current.getBoundingClientRect();
                inTabsBarByPos = e.clientX >= rect.left && e.clientX <= rect.right && e.clientY >= rect.top && e.clientY <= rect.bottom;
            }
            const inTabsBar = inTabsBarByTarget || inTabsBarByPos;
            const menuOpen = contextMenuOpenRef.current;
            if (inTabsBar || menuOpen) {
                e.preventDefault();
            }
        };
        document.addEventListener('contextmenu', onDocContextMenu, true);
        return () => document.removeEventListener('contextmenu', onDocContextMenu, true);
    }, []);
    const t = useCallback((key, defaultValue) => {
        const translated = translate(key);
        return translated === key ? defaultValue : translated;
    }, [translate]);
    // 处理待执行的导航
    useEffect(() => {
        if (pendingNavigationRef.current) {
            navigate(pendingNavigationRef.current);
@@ -103,7 +116,6 @@
        }
    });
    // 根据路径查找菜单名称
    const findMenuName = useCallback((path, menus) => {
        if (!menus || !path) return null;
        const cleanPath = path.replace(/^\//, '');
@@ -124,51 +136,30 @@
        return searchMenu(menus);
    }, []);
    // 获取标签页显示名称
    const getTabLabel = useCallback((tab) => {
        // 固定标签页直接返回翻译后的名称
        if (tab.name) {
            return translate(tab.name);
        }
        // 检查特殊页面
        if (tab.name) return translate(tab.name);
        const normalizedPath = normalizePath(tab.path);
        if (SPECIAL_PAGES[normalizedPath]) {
            return translate(SPECIAL_PAGES[normalizedPath].name);
        }
        // 动态标签页尝试从权限中获取名称
        if (permissions) {
            const menuName = findMenuName(tab.path, permissions);
            if (menuName) {
                return translate(menuName);
            }
            if (menuName) return translate(menuName);
        }
        // 默认返回路径
        return tab.path.replace(/^\//, '').split('/')[0] || tab.path;
    }, [translate, permissions, findMenuName]);
    // 监听路由变化,添加新标签页
    useEffect(() => {
        const currentPath = location.pathname;
        // 忽略特定路径
        if (IGNORED_PATHS.includes(currentPath)) {
            return;
        }
        if (IGNORED_PATHS.includes(currentPath)) return;
        setTabs(prevTabs => {
            // 检查是否已有相同资源的标签页
            const existingTabIndex = prevTabs.findIndex(tab =>
                tab.path === currentPath || isSameResource(tab.path, currentPath)
            );
            if (existingTabIndex >= 0) {
                // 如果路径完全相同,不需要更新
                if (prevTabs[existingTabIndex].path === currentPath) {
                    return prevTabs;
                }
                // 更新现有标签页的路径(保持标签位置)
                if (prevTabs[existingTabIndex].path === currentPath) return prevTabs;
                const updatedTabs = [...prevTabs];
                updatedTabs[existingTabIndex] = {
                    ...updatedTabs[existingTabIndex],
@@ -178,19 +169,13 @@
                return updatedTabs;
            }
            // 查找菜单名称
            let menuName = null;
            if (permissions) {
                menuName = findMenuName(currentPath, permissions);
            }
            // 检查特殊页面
            if (permissions) menuName = findMenuName(currentPath, permissions);
            const normalizedPath = normalizePath(currentPath);
            if (SPECIAL_PAGES[normalizedPath] && !menuName) {
                menuName = SPECIAL_PAGES[normalizedPath].name;
            }
            // 添加新标签页
            const newTab = {
                path: currentPath,
                name: menuName,
@@ -202,15 +187,17 @@
        });
    }, [location.pathname, permissions, findMenuName]);
    // 切换标签页
    const handleTabChange = (event, newValue) => {
        if (justFinishedDragRef.current) {
            justFinishedDragRef.current = false;
            return;
        }
        const targetTab = tabs[newValue];
        if (targetTab && targetTab.path !== location.pathname) {
            navigate(targetTab.path);
        }
    };
    // 关闭标签页
    const handleCloseTab = (event, tabPath) => {
        event.stopPropagation();
        event.preventDefault();
@@ -218,9 +205,7 @@
        const tabIndex = tabs.findIndex(tab => tab.path === tabPath);
        const newTabs = tabs.filter(tab => tab.path !== tabPath);
        // 如果关闭的是当前标签页,需要导航到其他标签页
        if (location.pathname === tabPath || isSameResource(location.pathname, tabPath)) {
            // 优先导航到左边的标签页,否则导航到右边的
            const newIndex = Math.min(tabIndex, newTabs.length - 1);
            if (newIndex >= 0 && newTabs[newIndex]) {
                pendingNavigationRef.current = newTabs[newIndex].path;
@@ -233,59 +218,37 @@
        setTabs(newTabs);
    };
    // 关闭所有标签页(除了dashboard)
    const handleCloseAll = () => {
        const dashboardTab = tabs.find(tab => tab.path === '/dashboard');
        const newTabs = [dashboardTab || { ...FIXED_TABS[0] }];
        saveTabs(newTabs);
        setTabs(newTabs);
        if (location.pathname !== '/dashboard') {
            navigate('/dashboard');
        }
    };
    // 关闭其他标签页(保留指定标签页)
    const handleCloseOthers = (keepTabPath) => {
        const keepTab = tabs.find(tab => tab.path === keepTabPath);
        if (!keepTab) {
            return;
        }
        // 保留指定标签页和dashboard(如果dashboard不是指定标签页)
        if (!keepTab) return;
        const dashboardTab = tabs.find(tab => tab.path === '/dashboard');
        const newTabs = [];
        // 如果指定标签页不是dashboard,确保dashboard也在列表中
        if (keepTab.path !== '/dashboard' && dashboardTab) {
            newTabs.push(dashboardTab);
        }
        // 添加指定标签页(如果还没有添加)
        if (!newTabs.some(tab => tab.path === keepTab.path)) {
            newTabs.push(keepTab);
        }
        saveTabs(newTabs);
        setTabs(newTabs);
        setContextMenu(null);
        setContextMenuTab(null);
    };
    //关闭左侧标签页(保留指定标签页及其右侧的所有标签页)
    const handleCloseLeft = (keepTabPath) => {
        const keepTabIndex = tabs.findIndex(tab => tab.path === keepTabPath);
        if (keepTabIndex < 0) {
            return;
        }
        // 保留指定标签页及其右侧的所有标签页
        const newTabs = tabs.slice(keepTabIndex);
        // 确保dashboard始终在第一位(如果存在)
        if (keepTabIndex < 0) return;
        let newTabs = tabs.slice(keepTabIndex);
        const dashboardTab = newTabs.find(tab => tab.path === '/dashboard');
        if (dashboardTab && newTabs[0].path !== '/dashboard') {
            const dashboardIndex = newTabs.findIndex(tab => tab.path === '/dashboard');
@@ -294,30 +257,22 @@
                newTabs.unshift(dashboardTab);
            }
        }
        saveTabs(newTabs);
        setTabs(newTabs);
        setContextMenu(null);
        setContextMenuTab(null);
    };
    // 关闭右侧标签页(保留指定标签页及其左侧的所有标签页)
    const handleCloseRight = (keepTabPath) => {
        const keepTabIndex = tabs.findIndex(tab => tab.path === keepTabPath);
        if (keepTabIndex < 0) {
            return;
        }
        // 保留指定标签页及其左侧的所有标签页
        if (keepTabIndex < 0) return;
        const newTabs = tabs.slice(0, keepTabIndex + 1);
        saveTabs(newTabs);
        setTabs(newTabs);
        setContextMenu(null);
        setContextMenuTab(null);
    };
    // 处理右键菜单
    const handleContextMenu = (event, tab) => {
        event.preventDefault();
        event.stopPropagation();
@@ -329,33 +284,24 @@
            canCloseRightForTab(tab.path) ||
            canCloseOthersForTab(tab.path);
        if (!hasItems) return;
        setContextMenu({
            mouseX: event.clientX + 2,
            mouseY: event.clientY - 6,
        });
        setContextMenu({ mouseX: event.clientX + 2, mouseY: event.clientY - 6 });
        setContextMenuTab(tab);
    };
    // 关闭右键菜单
    const handleCloseContextMenu = () => {
        setContextMenu(null);
        setContextMenuTab(null);
    };
    // 右键菜单:关闭当前标签
    const handleCloseCurrentTab = () => {
        if (!contextMenuTab) {
            handleCloseContextMenu();
            return;
        }
        const tabPath = contextMenuTab.path;
        const tabIndex = tabs.findIndex(tab => tab.path === tabPath);
        const newTabs = tabs.filter(tab => tab.path !== tabPath);
        // 如果关闭的是当前标签页,需要导航到其他标签页
        if (location.pathname === tabPath || isSameResource(location.pathname, tabPath)) {
            // 优先导航到左边的标签页,否则导航到右边的
            const newIndex = Math.min(tabIndex, newTabs.length - 1);
            if (newIndex >= 0 && newTabs[newIndex]) {
                pendingNavigationRef.current = newTabs[newIndex].path;
@@ -363,73 +309,148 @@
                pendingNavigationRef.current = '/dashboard';
            }
        }
        saveTabs(newTabs);
        setTabs(newTabs);
        handleCloseContextMenu();
    };
    // 右键菜单:关闭其他标签
    const handleCloseOtherTabs = () => {
        if (contextMenuTab) {
            handleCloseOthers(contextMenuTab.path);
        }
        if (contextMenuTab) handleCloseOthers(contextMenuTab.path);
    };
    // 右键菜单:关闭左侧标签
    const handleCloseLeftTabs = () => {
        if (contextMenuTab) {
            handleCloseLeft(contextMenuTab.path);
        }
        if (contextMenuTab) handleCloseLeft(contextMenuTab.path);
    };
    // 右键菜单:关闭右侧标签
    const handleCloseRightTabs = () => {
        if (contextMenuTab) {
            handleCloseRight(contextMenuTab.path);
        }
        if (contextMenuTab) handleCloseRight(contextMenuTab.path);
    };
    const canCloseOthersForTab = (tabPath) => {
        return tabs.filter(tab => {
            const isTarget = tab.path === tabPath || isSameResource(tab.path, tabPath);
            return !isTarget && tab.closable;
        }).length > 0;
    };
    const canCloseLeftForTab = (tabPath) => {
        const tabIndex = tabs.findIndex(tab => tab.path === tabPath || isSameResource(tab.path, tabPath));
        if (tabIndex <= 0) return false;
        return tabs.slice(0, tabIndex).some(tab => tab.closable);
    };
    const canCloseRightForTab = (tabPath) => {
        const tabIndex = tabs.findIndex(tab => tab.path === tabPath || isSameResource(tab.path, tabPath));
        if (tabIndex < 0 || tabIndex >= tabs.length - 1) return false;
        return tabs.slice(tabIndex + 1).some(tab => tab.closable);
    };
    // 获取当前标签页索引
    const currentTabIndex = tabs.findIndex(tab =>
        tab.path === location.pathname || isSameResource(tab.path, location.pathname)
    );
    const activeIndex = currentTabIndex >= 0 ? currentTabIndex : 0;
    // 判断是否有可关闭的标签
    const hasClosableTabs = tabs.some(tab => tab.closable);
    // 判断指定标签页是否可以关闭其他标签
    const canCloseOthersForTab = (tabPath) => {
        return tabs.filter(tab => {
            const isTargetTab = tab.path === tabPath || isSameResource(tab.path, tabPath);
            return !isTargetTab && tab.closable;
        }).length > 0;
    };
    // 右键打开时,点击菜单和标签栏外关闭
    const menuPaperRef = useRef(null);
    useEffect(() => {
        if (contextMenu === null) return;
        const onDocClick = (e) => {
            const inMenu = menuPaperRef.current && menuPaperRef.current.contains(e.target);
            const inTabsBar = tabsBarRef.current && tabsBarRef.current.contains(e.target);
            if (!inMenu && !inTabsBar) {
                setContextMenu(null);
                setContextMenuTab(null);
            }
        };
        document.addEventListener('mousedown', onDocClick, true);
        return () => document.removeEventListener('mousedown', onDocClick, true);
    }, [contextMenu]);
    // 判断指定标签页是否可以关闭左侧标签
    const canCloseLeftForTab = (tabPath) => {
        const tabIndex = tabs.findIndex(tab => tab.path === tabPath || isSameResource(tab.path, tabPath));
        if (tabIndex <= 0) {
            return false; // 没有左侧标签或这是第一个标签
    const clearLongPressTimer = useCallback(() => {
        if (longPressTimerRef.current) {
            clearTimeout(longPressTimerRef.current);
            longPressTimerRef.current = null;
        }
        // 检查左侧是否有可关闭的标签(排除dashboard)
        return tabs.slice(0, tabIndex).some(tab => tab.closable);
    };
        longPressIndexRef.current = null;
    }, []);
    // 判断指定标签页是否可以关闭右侧标签
    const canCloseRightForTab = (tabPath) => {
        const tabIndex = tabs.findIndex(tab => tab.path === tabPath || isSameResource(tab.path, tabPath));
        if (tabIndex < 0 || tabIndex >= tabs.length - 1) {
            return false; // 没有右侧标签或这是最后一个标签
        }
        // 检查右侧是否有可关闭的标签
        return tabs.slice(tabIndex + 1).some(tab => tab.closable);
    };
    const handleTabPointerDown = useCallback((e, index) => {
        if (index < 0) return;
        longPressIndexRef.current = index;
        longPressTimerRef.current = setTimeout(() => {
            longPressTimerRef.current = null;
            setDraggingIndex(index);
            setDropIndicatorIndex(index);
        }, LONG_PRESS_MS);
    }, []);
    const handleTabPointerUp = useCallback(() => {
        clearLongPressTimer();
    }, [clearLongPressTimer]);
    useEffect(() => {
        if (draggingIndex === null) return;
        const getDropIndex = (clientX) => {
            const nodes = tabsBarRef.current?.querySelectorAll('[data-tab-index]');
            if (!nodes?.length) return draggingIndex;
            for (let i = 0; i < nodes.length; i++) {
                const rect = nodes[i].getBoundingClientRect();
                const mid = rect.left + rect.width / 2;
                if (clientX <= mid) {
                    const drop = i;
                    if (draggingIndex === 0) return drop <= 0 ? 0 : draggingIndex;
                    return drop <= 0 ? 1 : drop;
                }
            }
            const drop = nodes.length;
            return draggingIndex === 0 ? 0 : drop;
        };
        const clientXFromEvent = (e) => e.clientX ?? e.touches?.[0]?.clientX;
        const onMove = (e) => {
            const x = clientXFromEvent(e);
            if (x != null) setDropIndicatorIndex(getDropIndex(x));
        };
        const onTouchMove = (e) => {
            e.preventDefault();
            onMove(e);
        };
        const onUp = () => {
            justFinishedDragRef.current = true;
            setDraggingIndex((di) => {
                setDropIndicatorIndex((dropIdx) => {
                    if (di !== null && dropIdx !== null && di !== dropIdx) {
                        const newTabs = [...tabs];
                        const [item] = newTabs.splice(di, 1);
                        const insertAt = dropIdx > di ? dropIdx - 1 : dropIdx;
                        newTabs.splice(insertAt, 0, item);
                        const dashboard = newTabs.find((t) => t.path === '/dashboard');
                        if (dashboard && newTabs[0].path !== '/dashboard') {
                            const idx = newTabs.indexOf(dashboard);
                            newTabs.splice(idx, 1);
                            newTabs.unshift(dashboard);
                        }
                        saveTabs(newTabs);
                        setTabs(newTabs);
                    }
                    return null;
                });
                return null;
            });
        };
        document.addEventListener('mousemove', onMove, true);
        document.addEventListener('mouseup', onUp, true);
        document.addEventListener('touchmove', onTouchMove, { passive: false, capture: true });
        document.addEventListener('touchend', onUp, true);
        document.addEventListener('touchcancel', onUp, true);
        return () => {
            document.removeEventListener('mousemove', onMove, true);
            document.removeEventListener('mouseup', onUp, true);
            document.removeEventListener('touchmove', onTouchMove, true);
            document.removeEventListener('touchend', onUp, true);
            document.removeEventListener('touchcancel', onUp, true);
        };
    }, [draggingIndex, tabs]);
    return (
        <Box
            ref={tabsBarRef}
            sx={{
                width: '100%',
                backgroundColor: '#fff',
@@ -461,12 +482,20 @@
                        key={tab.path}
                        label={
                            <Box
                                data-tab-index={index}
                                onContextMenu={(e) => handleContextMenu(e, tab)}
                                onMouseDown={(e) => handleTabPointerDown(e, index)}
                                onMouseUp={handleTabPointerUp}
                                onMouseLeave={handleTabPointerUp}
                                onTouchStart={(e) => handleTabPointerDown(e, index)}
                                onTouchEnd={handleTabPointerUp}
                                onTouchCancel={handleTabPointerUp}
                                sx={{
                                    display: 'flex',
                                    alignItems: 'center',
                                    gap: 0.5,
                                    width: '100%',
                                    ...(draggingIndex === index && { opacity: 0.7 }),
                                }}
                            >
                                {tab.path === '/dashboard' && (
@@ -485,18 +514,16 @@
                                                display: 'inline-flex',
                                                alignItems: 'center',
                                                justifyContent: 'center',
                                                p: 0.35,
                                                p: 0.25,
                                                ml: 0.5,
                                                borderRadius: '50%',
                                                cursor: 'pointer',
                                                color: 'inherit',
                                                '&:hover': {
                                                    backgroundColor: 'rgba(0, 0, 0, 0.1)',
                                                    color: '#d32f2f',
                                                },
                                            }}
                                        >
                                            <CloseIcon sx={{ fontSize: 16 }} />
                                            <CloseIcon sx={{ fontSize: 14 }} />
                                        </Box>
                                    </Tooltip>
                                )}
@@ -523,7 +550,6 @@
                    />
                ))}
            </Tabs>
            {/* 清除所有标签按钮 */}
            {hasClosableTabs && (
                <Box sx={{ display: 'flex', alignItems: 'center', pr: 1 }}>
                    <Divider orientation="vertical" flexItem sx={{ mx: 0.5, height: 20, alignSelf: 'center' }} />
@@ -545,7 +571,6 @@
                    </Tooltip>
                </Box>
            )}
            {/* 右键菜单 */}
            <Menu
                open={contextMenu !== null}
                onClose={handleCloseContextMenu}
@@ -556,8 +581,12 @@
                        : undefined
                }
                disableScrollLock
                ModalProps={{ disablePortal: true }}
                ModalProps={{
                    disablePortal: true,
                    BackdropProps: { sx: { pointerEvents: 'none' } },
                }}
                PaperProps={{
                    ref: menuPaperRef,
                    sx: {
                        minWidth: 120,
                        borderRadius: '8px',
@@ -566,59 +595,20 @@
                        py: 0.5,
                    },
                }}
                MenuListProps={{
                    sx: { py: 0 },
                }}
                MenuListProps={{ sx: { py: 0 } }}
            >
                {contextMenuTab && contextMenuTab.closable && (
                    <MenuItem
                        onClick={handleCloseCurrentTab}
                        sx={{
                            fontSize: '0.8125rem',
                            py: 0.75,
                            px: 1.5,
                            minHeight: 'auto',
                        }}
                    >
                        {t('ra.action.close', '关闭当前标签')}
                    </MenuItem>
                )}
                {contextMenuTab && canCloseLeftForTab(contextMenuTab.path) && (
                    <MenuItem
                        onClick={handleCloseLeftTabs}
                        sx={{
                            fontSize: '0.8125rem',
                            py: 0.75,
                            px: 1.5,
                            minHeight: 'auto',
                        }}
                    >
                    <MenuItem onClick={handleCloseLeftTabs} sx={{ fontSize: '0.8125rem', py: 0.75, px: 1.5, minHeight: 'auto' }}>
                        {t('ra.action.closeLeft', '关闭左侧标签')}
                    </MenuItem>
                )}
                {contextMenuTab && canCloseRightForTab(contextMenuTab.path) && (
                    <MenuItem
                        onClick={handleCloseRightTabs}
                        sx={{
                            fontSize: '0.8125rem',
                            py: 0.75,
                            px: 1.5,
                            minHeight: 'auto',
                        }}
                    >
                    <MenuItem onClick={handleCloseRightTabs} sx={{ fontSize: '0.8125rem', py: 0.75, px: 1.5, minHeight: 'auto' }}>
                        {t('ra.action.closeRight', '关闭右侧标签')}
                    </MenuItem>
                )}
                {contextMenuTab && canCloseOthersForTab(contextMenuTab.path) && (
                    <MenuItem
                        onClick={handleCloseOtherTabs}
                        sx={{
                            fontSize: '0.8125rem',
                            py: 0.75,
                            px: 1.5,
                            minHeight: 'auto',
                        }}
                    >
                    <MenuItem onClick={handleCloseOtherTabs} sx={{ fontSize: '0.8125rem', py: 0.75, px: 1.5, minHeight: 'auto' }}>
                        {t('ra.action.closeOthers', '关闭其他标签')}
                    </MenuItem>
                )}
rsf-open-api/src/main/java/com/vincent/rsf/openApi/config/ApiSecurityConfig.java
@@ -20,7 +20,7 @@
    public FilterRegistrationBean<AppIdAuthenticationFilter> apiAuthenticationFilter() {
        FilterRegistrationBean<AppIdAuthenticationFilter> registrationBean = new FilterRegistrationBean<>();
        registrationBean.setFilter(appIdAuthenticationFilter);
        registrationBean.addUrlPatterns("/api/*", "/erp/*", "/cloudwms/*", "/mes/*", "/agv/*");
        registrationBean.addUrlPatterns("/api/*"/*, "/erp/*", "/cloudwms/*"*/, "/mes/*", "/agv/*");
        registrationBean.setName("apiAuthenticationFilter");
        registrationBean.setOrder(1);
        return registrationBean;
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/OutStockServiceImpl.java
@@ -943,13 +943,14 @@
            if (issued.compareTo(ISSUED_TOLERANCE) <= 0) {
                continue;
            }
            // 出库分配不按批次过滤,仅按物料编码查库位,便于正常检测到可出库库存
            List<LocItem> locItems = new ArrayList<>();
            if (WaveRuleType.Efficiency_First.type.equals(waveRule.getType())) {
                locItems = LocManageUtil.getEfficiencyFirstItemList(wkOrderItem.getMatnrCode(), wkOrderItem.getSplrBatch(), wkOrderItem.getAnfme());
                locItems = LocManageUtil.getEfficiencyFirstItemList(wkOrderItem.getMatnrCode(), null, wkOrderItem.getAnfme());
            } else if (WaveRuleType.First_In_First_Out.type.equals(waveRule.getType())) {
                locItems = LocManageUtil.getFirstInFirstOutItemList(wkOrderItem.getMatnrCode(), wkOrderItem.getSplrBatch(), wkOrderItem.getAnfme());
                locItems = LocManageUtil.getFirstInFirstOutItemList(wkOrderItem.getMatnrCode(), null, wkOrderItem.getAnfme());
            } else {
                locItems = LocManageUtil.getFirstInFirstOutItemList(wkOrderItem.getMatnrCode(), wkOrderItem.getSplrBatch(), wkOrderItem.getAnfme());
                locItems = LocManageUtil.getFirstInFirstOutItemList(wkOrderItem.getMatnrCode(), null, wkOrderItem.getAnfme());
            }
            for (LocItem locItem : locItems) {
                Loc loc = locService.getById(locItem.getLocId());