fix(frontend): ship pending card-view animation and interaction refinements

This commit is contained in:
sagitchu
2026-02-22 15:52:48 +08:00
parent 79c28103d5
commit d483258eef
16 changed files with 640 additions and 503 deletions
+77 -69
View File
@@ -19,7 +19,6 @@ import {
useSortable,
} from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
import { Card, CardBody, CardHeader } from "@/shadcn-bridge/heroui/card";
import { Button } from "@/shadcn-bridge/heroui/button";
import { Input, Textarea } from "@/shadcn-bridge/heroui/input";
@@ -230,9 +229,9 @@ export default function TunnelPage() {
trafficRatio: tunnel.trafficRatio,
inIp: tunnel.inIp
? tunnel.inIp
.split(",")
.map((ip: string) => ip.trim())
.join("\n")
.split(",")
.map((ip: string) => ip.trim())
.join("\n")
: "",
ipPreference: tunnel.ipPreference || "",
status: tunnel.status,
@@ -667,13 +666,16 @@ export default function TunnelPage() {
} = useSortable({ id });
const style: React.CSSProperties = {
transform: transform ? CSS.Transform.toString(transform) : undefined,
transform: transform
? CSS.Transform.toString({
...transform,
x: Math.round(transform.x),
y: Math.round(transform.y),
})
: undefined,
transition: isDragging ? undefined : transition || undefined,
opacity: isDragging ? 0.5 : 1,
willChange: "transform",
backfaceVisibility: "hidden",
WebkitFontSmoothing: "antialiased",
MozOsxFontSmoothing: "grayscale",
willChange: isDragging ? "transform" : undefined,
};
return (
@@ -1384,9 +1386,9 @@ export default function TunnelPage() {
.filter((id) => id !== -1)
.map((id) => id.toString()),
]}
dropdownPlacement="top"
label="节点"
placeholder="选择节点(可多选)"
dropdownPlacement="top"
selectedKeys={groupNodes
.filter((ct) => ct.nodeId !== -1)
.map((ct) => ct.nodeId.toString())}
@@ -1453,14 +1455,14 @@ export default function TunnelPage() {
{form.inNodeId.some(
(ct) => ct.nodeId === node.id,
) && (
<Chip
color="warning"
size="sm"
variant="flat"
>
已选为入口
</Chip>
)}
<Chip
color="warning"
size="sm"
variant="flat"
>
已选为入口
</Chip>
)}
{form.outNodeId &&
form.outNodeId.some(
(ct) => ct.nodeId === node.id,
@@ -1483,14 +1485,14 @@ export default function TunnelPage() {
ct.nodeId !== -1,
),
) && (
<Chip
color="primary"
size="sm"
variant="flat"
>
已选为其他跳
</Chip>
)}
<Chip
color="primary"
size="sm"
variant="flat"
>
已选为其他跳
</Chip>
)}
</div>
</div>
</SelectItem>
@@ -1600,16 +1602,16 @@ export default function TunnelPage() {
id.toString(),
),
]}
dropdownPlacement="top"
errorMessage={errors.outNodeId}
isInvalid={!!errors.outNodeId}
label="节点"
placeholder="请选择出口节点(可多选)"
dropdownPlacement="top"
selectedKeys={
form.outNodeId
? form.outNodeId
.filter((ct) => ct.nodeId !== -1)
.map((ct) => ct.nodeId.toString())
.filter((ct) => ct.nodeId !== -1)
.map((ct) => ct.nodeId.toString())
: []
}
selectionMode="multiple"
@@ -1676,25 +1678,25 @@ export default function TunnelPage() {
{form.inNodeId.some(
(ct) => ct.nodeId === node.id,
) && (
<Chip
color="warning"
size="sm"
variant="flat"
>
已选为入口
</Chip>
)}
<Chip
color="warning"
size="sm"
variant="flat"
>
已选为入口
</Chip>
)}
{getSelectedChainNodeIds().includes(
node.id,
) && (
<Chip
color="primary"
size="sm"
variant="flat"
>
已选为转发链
</Chip>
)}
<Chip
color="primary"
size="sm"
variant="flat"
>
已选为转发链
</Chip>
)}
</div>
</div>
</SelectItem>
@@ -2057,18 +2059,20 @@ export default function TunnelPage() {
return (
<tr
key={index}
className={`hover:bg-default-50 dark:hover:bg-gray-700/50 ${result.success
? "bg-white dark:bg-gray-800"
: "bg-danger-50 dark:bg-danger-900/30"
}`}
className={`hover:bg-default-50 dark:hover:bg-gray-700/50 ${
result.success
? "bg-white dark:bg-gray-800"
: "bg-danger-50 dark:bg-danger-900/30"
}`}
>
<td className="px-3 py-2">
<div className="flex items-center gap-2">
<span
className={`w-5 h-5 rounded-full flex items-center justify-center text-xs ${result.success
? "bg-success text-white"
: "bg-danger text-white"
}`}
className={`w-5 h-5 rounded-full flex items-center justify-center text-xs ${
result.success
? "bg-success text-white"
: "bg-danger text-white"
}`}
>
{result.success ? "✓" : "✗"}
</span>
@@ -2110,10 +2114,11 @@ export default function TunnelPage() {
<td className="px-3 py-2 text-center">
{result.success ? (
<span
className={`font-semibold ${(result.packetLoss || 0) > 0
? "text-warning"
: "text-success"
}`}
className={`font-semibold ${
(result.packetLoss || 0) > 0
? "text-warning"
: "text-success"
}`}
>
{result.packetLoss?.toFixed(1)}%
</span>
@@ -2226,17 +2231,19 @@ export default function TunnelPage() {
return (
<div
key={index}
className={`border rounded-lg p-3 ${result.success
? "border-divider bg-white dark:bg-gray-800"
: "border-danger-200 dark:border-danger-300/30 bg-danger-50 dark:bg-danger-900/30"
}`}
className={`border rounded-lg p-3 ${
result.success
? "border-divider bg-white dark:bg-gray-800"
: "border-danger-200 dark:border-danger-300/30 bg-danger-50 dark:bg-danger-900/30"
}`}
>
<div className="flex items-start gap-2 mb-2">
<span
className={`w-6 h-6 rounded-full flex items-center justify-center text-xs flex-shrink-0 ${result.success
? "bg-success text-white"
: "bg-danger text-white"
}`}
className={`w-6 h-6 rounded-full flex items-center justify-center text-xs flex-shrink-0 ${
result.success
? "bg-success text-white"
: "bg-danger text-white"
}`}
>
{result.success ? "✓" : "✗"}
</span>
@@ -2272,10 +2279,11 @@ export default function TunnelPage() {
</div>
<div className="text-center">
<div
className={`text-lg font-bold ${(result.packetLoss || 0) > 0
? "text-warning"
: "text-success"
}`}
className={`text-lg font-bold ${
(result.packetLoss || 0) > 0
? "text-warning"
: "text-success"
}`}
>
{result.packetLoss?.toFixed(1)}%
</div>