增加隐藏详细信息

This commit is contained in:
2025-10-26 12:49:41 +08:00
parent 3ee320b2b4
commit f04594e56f
2 changed files with 91 additions and 11 deletions

View File

@@ -5,6 +5,8 @@ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Progress } from '@/components/ui/progress'; import { Progress } from '@/components/ui/progress';
import { Alert, AlertDescription } from '@/components/ui/alert'; import { Alert, AlertDescription } from '@/components/ui/alert';
import { Badge } from '@/components/ui/badge';
import { CardDescription } from '@/components/ui/card';
import { import {
Dialog, Dialog,
DialogContent, DialogContent,
@@ -43,7 +45,9 @@ import {
Save, Save,
X, X,
Download, Download,
Upload Upload,
GripVertical,
Globe,
} from 'lucide-react'; } from 'lucide-react';
import { import {
DndContext, DndContext,
@@ -53,6 +57,9 @@ import {
useSensor, useSensor,
useSensors, useSensors,
DragEndEvent, DragEndEvent,
DragStartEvent,
DragOverlay,
defaultDropAnimationSideEffects,
} from '@dnd-kit/core'; } from '@dnd-kit/core';
import { import {
arrayMove, arrayMove,
@@ -106,20 +113,36 @@ const RelayStationManager: React.FC<RelayStationManagerProps> = ({ onBack }) =>
const [quotaData, setQuotaData] = useState<Record<string, PackycodeUserQuota>>({}); const [quotaData, setQuotaData] = useState<Record<string, PackycodeUserQuota>>({});
const [loadingQuota, setLoadingQuota] = useState<Record<string, boolean>>({}); const [loadingQuota, setLoadingQuota] = useState<Record<string, boolean>>({});
// 拖拽状态
const [activeStation, setActiveStation] = useState<RelayStation | null>(null);
const { t } = useTranslation(); const { t } = useTranslation();
// 拖拽传感器配置 // 拖拽传感器配置
const sensors = useSensors( const sensors = useSensors(
useSensor(PointerSensor), useSensor(PointerSensor, {
activationConstraint: {
distance: 8, // 需要拖动8px才激活避免误触
},
}),
useSensor(KeyboardSensor, { useSensor(KeyboardSensor, {
coordinateGetter: sortableKeyboardCoordinates, 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 handleDragEnd = async (event: DragEndEvent) => {
const { active, over } = event; const { active, over } = event;
setActiveStation(null); // 清除拖拽状态
if (over && active.id !== over.id) { if (over && active.id !== over.id) {
const oldIndex = stations.findIndex(station => station.id === active.id); const oldIndex = stations.findIndex(station => station.id === active.id);
const newIndex = stations.findIndex(station => station.id === over.id); const newIndex = stations.findIndex(station => station.id === over.id);
@@ -140,6 +163,17 @@ const RelayStationManager: React.FC<RelayStationManagerProps> = ({ onBack }) =>
} }
}; };
// 自定义拖拽动画
const dropAnimationConfig = {
sideEffects: defaultDropAnimationSideEffects({
styles: {
active: {
opacity: '0.4',
},
},
}),
};
// Token 脱敏函数 // Token 脱敏函数
const maskToken = (token: string): string => { const maskToken = (token: string): string => {
if (!token || token.length <= 8) { if (!token || token.length <= 8) {
@@ -855,6 +889,7 @@ const RelayStationManager: React.FC<RelayStationManagerProps> = ({ onBack }) =>
<DndContext <DndContext
sensors={sensors} sensors={sensors}
collisionDetection={closestCenter} collisionDetection={closestCenter}
onDragStart={handleDragStart}
onDragEnd={handleDragEnd} onDragEnd={handleDragEnd}
> >
<SortableContext <SortableContext
@@ -892,6 +927,38 @@ const RelayStationManager: React.FC<RelayStationManagerProps> = ({ onBack }) =>
)} )}
</div> </div>
</SortableContext> </SortableContext>
{/* 拖拽预览层 */}
<DragOverlay dropAnimation={dropAnimationConfig}>
{activeStation ? (
<Card className="shadow-2xl ring-2 ring-blue-500 rotate-3 cursor-grabbing">
<CardHeader className="pb-2 pt-3 px-3">
<div className="flex justify-between items-center">
<div className="flex items-center flex-1 min-w-0 mr-2">
<div className="mr-2 flex-shrink-0">
<GripVertical className="h-4 w-4 text-blue-500" />
</div>
<div className="flex-1 min-w-0">
<CardTitle className="text-sm font-medium">{activeStation.name}</CardTitle>
<CardDescription className="text-xs mt-0.5">
{getAdapterDisplayName(activeStation.adapter)}
</CardDescription>
</div>
</div>
<Badge variant={activeStation.enabled ? "default" : "secondary"} className="text-xs">
{activeStation.enabled ? '已启用' : '已禁用'}
</Badge>
</div>
</CardHeader>
<CardContent className="pt-1 pb-3 px-3">
<div className="flex items-center text-xs text-muted-foreground">
<Globe className="mr-1.5 h-3 w-3 flex-shrink-0" />
<span className="truncate">{activeStation.api_url}</span>
</div>
</CardContent>
</Card>
) : null}
</DragOverlay>
</DndContext> </DndContext>
{/* 编辑对话框 */} {/* 编辑对话框 */}

View File

@@ -50,6 +50,7 @@ export const SortableStationItem: React.FC<SortableStationItemProps> = ({
transform, transform,
transition, transition,
isDragging, isDragging,
isOver,
} = useSortable({ id: station.id }); } = useSortable({ id: station.id });
// 展开/收起状态,从 localStorage 读取 // 展开/收起状态,从 localStorage 读取
@@ -66,24 +67,34 @@ export const SortableStationItem: React.FC<SortableStationItemProps> = ({
const style = { const style = {
transform: CSS.Transform.toString(transform), transform: CSS.Transform.toString(transform),
transition, transition,
opacity: isDragging ? 0.5 : 1, opacity: isDragging ? 0.4 : 1,
}; };
// 是否有详情内容需要显示 // 是否有详情内容需要显示
const hasDetails = station.description || station.adapter === 'packycode'; const hasDetails = station.description || station.adapter === 'packycode';
return ( return (
<Card ref={setNodeRef} style={style} className="relative"> <Card
ref={setNodeRef}
style={style}
className={`relative transition-all duration-200 ${
isDragging
? 'shadow-2xl ring-2 ring-blue-500 scale-105 z-50'
: isOver
? 'ring-2 ring-blue-400 ring-offset-2 bg-blue-50 dark:bg-blue-950/50 scale-102'
: 'hover:shadow-md'
}`}
>
<CardHeader className="pb-2 pt-3 px-3"> <CardHeader className="pb-2 pt-3 px-3">
<div className="flex justify-between items-center"> <div className="flex justify-between items-center">
<div className="flex items-center flex-1 min-w-0 mr-2"> <div
<button className="flex items-center flex-1 min-w-0 mr-2 cursor-grab active:cursor-grabbing"
className="cursor-grab active:cursor-grabbing mr-2 touch-none" {...attributes}
{...attributes} {...listeners}
{...listeners} >
> <div className="mr-2 flex-shrink-0">
<GripVertical className="h-4 w-4 text-muted-foreground hover:text-foreground transition-colors" /> <GripVertical className="h-4 w-4 text-muted-foreground hover:text-foreground transition-colors" />
</button> </div>
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<CardTitle className="text-sm font-medium">{station.name}</CardTitle> <CardTitle className="text-sm font-medium">{station.name}</CardTitle>
<CardDescription className="text-xs mt-0.5"> <CardDescription className="text-xs mt-0.5">
@@ -97,6 +108,7 @@ export const SortableStationItem: React.FC<SortableStationItemProps> = ({
variant="ghost" variant="ghost"
size="icon" size="icon"
className="h-8 w-8" className="h-8 w-8"
disabled={isDragging}
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
setSelectedStation(station); setSelectedStation(station);
@@ -109,6 +121,7 @@ export const SortableStationItem: React.FC<SortableStationItemProps> = ({
variant="ghost" variant="ghost"
size="icon" size="icon"
className="h-8 w-8 text-red-500 hover:text-red-700" className="h-8 w-8 text-red-500 hover:text-red-700"
disabled={isDragging}
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
openDeleteDialog(station); openDeleteDialog(station);