From f04594e56fee267775fe95a6f0ff842c0dcc642f Mon Sep 17 00:00:00 2001 From: YoVinchen Date: Sun, 26 Oct 2025 12:49:41 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E9=9A=90=E8=97=8F=E8=AF=A6?= =?UTF-8?q?=E7=BB=86=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/RelayStationManager.tsx | 71 +++++++++++++++++++++++++- src/components/SortableStationItem.tsx | 31 +++++++---- 2 files changed, 91 insertions(+), 11 deletions(-) diff --git a/src/components/RelayStationManager.tsx b/src/components/RelayStationManager.tsx index 2427789..33eec7c 100644 --- a/src/components/RelayStationManager.tsx +++ b/src/components/RelayStationManager.tsx @@ -5,6 +5,8 @@ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Progress } from '@/components/ui/progress'; import { Alert, AlertDescription } from '@/components/ui/alert'; +import { Badge } from '@/components/ui/badge'; +import { CardDescription } from '@/components/ui/card'; import { Dialog, DialogContent, @@ -43,7 +45,9 @@ import { Save, X, Download, - Upload + Upload, + GripVertical, + Globe, } from 'lucide-react'; import { DndContext, @@ -53,6 +57,9 @@ import { useSensor, useSensors, DragEndEvent, + DragStartEvent, + DragOverlay, + defaultDropAnimationSideEffects, } from '@dnd-kit/core'; import { arrayMove, @@ -106,20 +113,36 @@ const RelayStationManager: React.FC = ({ onBack }) => const [quotaData, setQuotaData] = useState>({}); const [loadingQuota, setLoadingQuota] = useState>({}); + // 拖拽状态 + const [activeStation, setActiveStation] = useState(null); + const { t } = useTranslation(); // 拖拽传感器配置 const sensors = useSensors( - useSensor(PointerSensor), + useSensor(PointerSensor, { + activationConstraint: { + distance: 8, // 需要拖动8px才激活,避免误触 + }, + }), useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates, }) ); + // 拖拽开始处理 + const handleDragStart = (event: DragStartEvent) => { + const { active } = event; + const station = stations.find(s => s.id === active.id); + setActiveStation(station || null); + }; + // 拖拽结束处理 const handleDragEnd = async (event: DragEndEvent) => { const { active, over } = event; + setActiveStation(null); // 清除拖拽状态 + if (over && active.id !== over.id) { const oldIndex = stations.findIndex(station => station.id === active.id); const newIndex = stations.findIndex(station => station.id === over.id); @@ -140,6 +163,17 @@ const RelayStationManager: React.FC = ({ onBack }) => } }; + // 自定义拖拽动画 + const dropAnimationConfig = { + sideEffects: defaultDropAnimationSideEffects({ + styles: { + active: { + opacity: '0.4', + }, + }, + }), + }; + // Token 脱敏函数 const maskToken = (token: string): string => { if (!token || token.length <= 8) { @@ -855,6 +889,7 @@ const RelayStationManager: React.FC = ({ onBack }) => = ({ onBack }) => )} + + {/* 拖拽预览层 */} + + {activeStation ? ( + + +
+
+
+ +
+
+ {activeStation.name} + + {getAdapterDisplayName(activeStation.adapter)} + +
+
+ + {activeStation.enabled ? '已启用' : '已禁用'} + +
+
+ +
+ + {activeStation.api_url} +
+
+
+ ) : null} +
{/* 编辑对话框 */} diff --git a/src/components/SortableStationItem.tsx b/src/components/SortableStationItem.tsx index b16bfec..3353f72 100644 --- a/src/components/SortableStationItem.tsx +++ b/src/components/SortableStationItem.tsx @@ -50,6 +50,7 @@ export const SortableStationItem: React.FC = ({ transform, transition, isDragging, + isOver, } = useSortable({ id: station.id }); // 展开/收起状态,从 localStorage 读取 @@ -66,24 +67,34 @@ export const SortableStationItem: React.FC = ({ const style = { transform: CSS.Transform.toString(transform), transition, - opacity: isDragging ? 0.5 : 1, + opacity: isDragging ? 0.4 : 1, }; // 是否有详情内容需要显示 const hasDetails = station.description || station.adapter === 'packycode'; return ( - +
-
- +
{station.name} @@ -97,6 +108,7 @@ export const SortableStationItem: React.FC = ({ variant="ghost" size="icon" className="h-8 w-8" + disabled={isDragging} onClick={(e) => { e.stopPropagation(); setSelectedStation(station); @@ -109,6 +121,7 @@ export const SortableStationItem: React.FC = ({ variant="ghost" size="icon" className="h-8 w-8 text-red-500 hover:text-red-700" + disabled={isDragging} onClick={(e) => { e.stopPropagation(); openDeleteDialog(station);