From d787e4b07af281708dc1b5f6a2291379181dc787 Mon Sep 17 00:00:00 2001 From: root Date: Mon, 2 Feb 2026 03:14:18 +0000 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=8B=96=E6=8B=BD=E6=8E=92?= =?UTF-8?q?=E5=BA=8F=E9=A1=BA=E6=BB=91=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vite-frontend/src/pages/forward.tsx | 61 ++++++++++++++++++----------- vite-frontend/src/pages/node.tsx | 36 +++++++++++------ vite-frontend/src/pages/tunnel.tsx | 36 +++++++++++------ 3 files changed, 88 insertions(+), 45 deletions(-) diff --git a/vite-frontend/src/pages/forward.tsx b/vite-frontend/src/pages/forward.tsx index febf8a1..5492ab4 100644 --- a/vite-frontend/src/pages/forward.tsx +++ b/vite-frontend/src/pages/forward.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect } from "react"; +import { useState, useEffect, useMemo } from "react"; import { Card, CardBody, CardHeader } from "@heroui/card"; import { Button } from "@heroui/button"; import { Input } from "@heroui/input"; @@ -15,7 +15,8 @@ import { DndContext, closestCenter, KeyboardSensor, - PointerSensor, + MouseSensor, + TouchSensor, useSensor, useSensors, DragEndEvent, @@ -353,8 +354,6 @@ export default function ForwardPage() { const userMap = new Map(); // 获取排序后的转发列表 - const sortedForwards = getSortedForwards(); - sortedForwards.forEach(forward => { const userKey = forward.userId ? forward.userId.toString() : 'unknown'; const userName = forward.userName || '未知用户'; @@ -855,7 +854,7 @@ export default function ForwardPage() { ); } else { // 直接显示模式下,过滤指定隧道的转发 - forwardsToExport = getSortedForwards().filter(forward => forward.tunnelId === selectedTunnelForExport); + forwardsToExport = sortedForwards.filter(forward => forward.tunnelId === selectedTunnelForExport); } if (forwardsToExport.length === 0) { @@ -1102,19 +1101,29 @@ export default function ForwardPage() { // 传感器配置 - 使用默认配置避免错误 const sensors = useSensors( - useSensor(PointerSensor), + useSensor(MouseSensor, { + activationConstraint: { + distance: 8, + }, + }), + useSensor(TouchSensor, { + activationConstraint: { + delay: 250, + tolerance: 8, + }, + }), useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates, }) ); // 根据排序顺序获取转发列表 - const getSortedForwards = (): Forward[] => { + const sortedForwards = useMemo((): Forward[] => { // 确保 forwards 数组存在且有效 if (!forwards || forwards.length === 0) { return []; } - + // 在平铺模式下,只显示当前用户的转发 let filteredForwards = forwards; if (viewMode === 'direct') { @@ -1123,43 +1132,48 @@ export default function ForwardPage() { filteredForwards = forwards.filter(forward => forward.userId === currentUserId); } } - + // 确保过滤后的转发列表有效 if (!filteredForwards || filteredForwards.length === 0) { return []; } - + // 优先使用数据库中的 inx 字段进行排序 - const sortedForwards = [...filteredForwards].sort((a, b) => { + const sortedByDb = [...filteredForwards].sort((a, b) => { const aInx = a.inx ?? 0; const bInx = b.inx ?? 0; return aInx - bInx; }); - + // 如果数据库中没有排序信息,则使用本地存储的顺序 - if (forwardOrder && forwardOrder.length > 0 && sortedForwards.every(f => f.inx === undefined || f.inx === 0)) { + if (forwardOrder && forwardOrder.length > 0 && sortedByDb.every(f => f.inx === undefined || f.inx === 0)) { const forwardMap = new Map(filteredForwards.map(f => [f.id, f])); const localSortedForwards: Forward[] = []; - + forwardOrder.forEach(id => { const forward = forwardMap.get(id); if (forward) { localSortedForwards.push(forward); } }); - + // 添加不在排序列表中的转发(新添加的) filteredForwards.forEach(forward => { if (!forwardOrder.includes(forward.id)) { localSortedForwards.push(forward); } }); - + return localSortedForwards; } - - return sortedForwards; - }; + + return sortedByDb; + }, [forwards, forwardOrder, viewMode]); + + const sortableForwardIds = useMemo( + () => sortedForwards.map(f => f.id).filter(id => id > 0), + [sortedForwards] + ); // 可拖拽的转发卡片组件 const SortableForwardCard = ({ forward }: { forward: Forward }) => { @@ -1179,8 +1193,9 @@ export default function ForwardPage() { const style = { transform: transform ? CSS.Transform.toString(transform) : undefined, - transition: transition || undefined, + transition: isDragging ? undefined : transition || undefined, opacity: isDragging ? 0.5 : 1, + willChange: 'transform', }; return ( @@ -1529,11 +1544,11 @@ export default function ForwardPage() { onDragStart={() => {}} // 添加空的 onDragStart 处理器 > f.id || 0).filter(id => id > 0)} + items={sortableForwardIds} strategy={rectSortingStrategy} >
- {getSortedForwards().map((forward) => ( + {sortedForwards.map((forward) => ( forward && forward.id ? ( ) : null @@ -2394,4 +2409,4 @@ export default function ForwardPage() {
); -} \ No newline at end of file +} diff --git a/vite-frontend/src/pages/node.tsx b/vite-frontend/src/pages/node.tsx index dbe407d..c71d220 100644 --- a/vite-frontend/src/pages/node.tsx +++ b/vite-frontend/src/pages/node.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect, useRef } from "react"; +import { useState, useEffect, useMemo, useRef } from "react"; import { Card, CardBody, CardHeader } from "@heroui/card"; import { Button } from "@heroui/button"; import { Input } from "@heroui/input"; @@ -16,7 +16,8 @@ import axios from 'axios'; import { DndContext, KeyboardSensor, - PointerSensor, + MouseSensor, + TouchSensor, type DragEndEvent, useSensor, useSensors, @@ -102,8 +103,9 @@ const SortableItem = ({ const style = { transform: transform ? CSS.Transform.toString(transform) : undefined, - transition: transition || undefined, + transition: isDragging ? undefined : transition || undefined, opacity: isDragging ? 0.5 : 1, + willChange: 'transform', }; return ( @@ -806,24 +808,34 @@ export default function NodePage() { // 传感器配置 const sensors = useSensors( - useSensor(PointerSensor), + useSensor(MouseSensor, { + activationConstraint: { + distance: 8, + }, + }), + useSensor(TouchSensor, { + activationConstraint: { + delay: 250, + tolerance: 8, + }, + }), useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates, }) ); // 根据排序顺序获取节点列表 - const getSortedNodes = (): Node[] => { + const sortedNodes = useMemo((): Node[] => { if (!nodeList || nodeList.length === 0) return []; - const sortedNodes = [...nodeList].sort((a, b) => { + const sortedByDb = [...nodeList].sort((a, b) => { const aInx = a.inx ?? 0; const bInx = b.inx ?? 0; return aInx - bInx; }); // 如果数据库中没有排序信息,则使用本地存储的顺序 - if (nodeOrder && nodeOrder.length > 0 && sortedNodes.every((n) => n.inx === undefined || n.inx === 0)) { + if (nodeOrder && nodeOrder.length > 0 && sortedByDb.every((n) => n.inx === undefined || n.inx === 0)) { const nodeMap = new Map(nodeList.map((n) => [n.id, n] as const)); const localSorted: Node[] = []; @@ -841,8 +853,10 @@ export default function NodePage() { return localSorted; } - return sortedNodes; - }; + return sortedByDb; + }, [nodeList, nodeOrder]); + + const sortableNodeIds = useMemo(() => sortedNodes.map((n) => n.id), [sortedNodes]); return ( @@ -900,11 +914,11 @@ export default function NodePage() { ) : ( n.id)} + items={sortableNodeIds} strategy={rectSortingStrategy} >
- {getSortedNodes().map((node) => ( + {sortedNodes.map((node) => ( {(listeners) => ( { + const sortedTunnels = useMemo((): Tunnel[] => { if (!tunnels || tunnels.length === 0) return []; - const sortedTunnels = [...tunnels].sort((a, b) => { + const sortedByDb = [...tunnels].sort((a, b) => { const aInx = a.inx ?? 0; const bInx = b.inx ?? 0; return aInx - bInx; }); // 如果数据库中没有排序信息,则使用本地存储的顺序 - if (tunnelOrder && tunnelOrder.length > 0 && sortedTunnels.every((t) => t.inx === undefined || t.inx === 0)) { + if (tunnelOrder && tunnelOrder.length > 0 && sortedByDb.every((t) => t.inx === undefined || t.inx === 0)) { const tunnelMap = new Map(tunnels.map((t) => [t.id, t] as const)); const localSorted: Tunnel[] = []; @@ -621,8 +632,10 @@ export default function TunnelPage() { return localSorted; } - return sortedTunnels; - }; + return sortedByDb; + }, [tunnels, tunnelOrder]); + + const sortableTunnelIds = useMemo(() => sortedTunnels.map((t) => t.id), [sortedTunnels]); const SortableItem = ({ id, @@ -642,8 +655,9 @@ export default function TunnelPage() { const style = { transform: transform ? CSS.Transform.toString(transform) : undefined, - transition: transition || undefined, + transition: isDragging ? undefined : transition || undefined, opacity: isDragging ? 0.5 : 1, + willChange: 'transform', }; return ( @@ -690,11 +704,11 @@ export default function TunnelPage() { {tunnels.length > 0 ? ( t.id)} + items={sortableTunnelIds} strategy={rectSortingStrategy} >
- {getSortedTunnels().map((tunnel) => { + {sortedTunnels.map((tunnel) => { const typeDisplay = getTypeDisplay(tunnel.type); return (