From b3a8cec76cd3d2d3aa6d470e1c28ec161bc1a16b Mon Sep 17 00:00:00 2001
From: chen.lin <1442464845@qq.com>
Date: 星期二, 10 三月 2026 17:22:44 +0800
Subject: [PATCH] 路径管理-初始化功能优化
---
rsf-admin/src/layout/TabsBar.jsx | 105 ++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 105 insertions(+), 0 deletions(-)
diff --git a/rsf-admin/src/layout/TabsBar.jsx b/rsf-admin/src/layout/TabsBar.jsx
index ab4f171..26d0c35 100644
--- a/rsf-admin/src/layout/TabsBar.jsx
+++ b/rsf-admin/src/layout/TabsBar.jsx
@@ -79,6 +79,8 @@
return normalizePath(path1) === normalizePath(path2);
};
+const LONG_PRESS_MS = 300;
+
const TabsBar = () => {
const location = useLocation();
const navigate = useNavigate();
@@ -91,6 +93,12 @@
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(() => {
@@ -226,6 +234,10 @@
// 鍒囨崲鏍囩椤�
const handleTabChange = (event, newValue) => {
+ if (justFinishedDragRef.current) {
+ justFinishedDragRef.current = false;
+ return;
+ }
const targetTab = tabs[newValue];
if (targetTab && targetTab.path !== location.pathname) {
navigate(targetTab.path);
@@ -415,6 +427,91 @@
return () => document.removeEventListener('mousedown', onDocClick, true);
}, [contextMenu]);
+ const clearLongPressTimer = useCallback(() => {
+ if (longPressTimerRef.current) {
+ clearTimeout(longPressTimerRef.current);
+ longPressTimerRef.current = null;
+ }
+ longPressIndexRef.current = null;
+ }, []);
+
+ 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}
@@ -449,12 +546,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' && (
--
Gitblit v1.9.1