增加隐藏详细信息
This commit is contained in:
@@ -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>
|
||||||
|
|
||||||
{/* 编辑对话框 */}
|
{/* 编辑对话框 */}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user