From de18a5db43131dde1ec8b7ac9327f1802f2120a9 Mon Sep 17 00:00:00 2001
From: chen.lin <1442464845@qq.com>
Date: 星期二, 10 三月 2026 09:03:21 +0800
Subject: [PATCH] 批次出库过滤忽略

---
 rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/OutStockServiceImpl.java |    7 
 rsf-open-api/src/main/java/com/vincent/rsf/openApi/config/ApiSecurityConfig.java              |    2 
 rsf-admin/src/layout/TabsBar.jsx                                                              |  380 ++++++++++++++++++++++++++---------------------------
 3 files changed, 190 insertions(+), 199 deletions(-)

diff --git a/rsf-admin/src/layout/TabsBar.jsx b/rsf-admin/src/layout/TabsBar.jsx
index b4641ae..14ed18e 100644
--- a/rsf-admin/src/layout/TabsBar.jsx
+++ b/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'];
 
-// 浠巐ocalStorage鑾峰彇宸蹭繚瀛樼殑鏍囩椤�
 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 @@
     }
 };
 
-// 瑙勮寖鍖栬矾寰� - 澶勭悊甯D鐨勮鎯�/缂栬緫椤甸潰
 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);
     };
 
-
-
-    // 鍏抽棴鎵�鏈夋爣绛鹃〉锛堥櫎浜哾ashboard锛�
     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锛堝鏋渄ashboard涓嶆槸鎸囧畾鏍囩椤碉級
+        if (!keepTab) return;
         const dashboardTab = tabs.find(tab => tab.path === '/dashboard');
         const newTabs = [];
-
-        // 濡傛灉鎸囧畾鏍囩椤典笉鏄痙ashboard锛岀‘淇漝ashboard涔熷湪鍒楄〃涓�
         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;
         }
-        // 妫�鏌ュ乏渚ф槸鍚︽湁鍙叧闂殑鏍囩锛堟帓闄ashboard锛�
-        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>
                 )}
diff --git a/rsf-open-api/src/main/java/com/vincent/rsf/openApi/config/ApiSecurityConfig.java b/rsf-open-api/src/main/java/com/vincent/rsf/openApi/config/ApiSecurityConfig.java
index b2b6c7d..ef6651c 100644
--- a/rsf-open-api/src/main/java/com/vincent/rsf/openApi/config/ApiSecurityConfig.java
+++ b/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;
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/OutStockServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/OutStockServiceImpl.java
index cc79c6d..4e97b9b 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/OutStockServiceImpl.java
+++ b/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());

--
Gitblit v1.9.1