From 23d2060742d5c871a0de1ba4e3e6ca95cbc86664 Mon Sep 17 00:00:00 2001 From: sagitchu Date: Sun, 15 Mar 2026 21:59:26 +0800 Subject: [PATCH] Restore BatchActionResultModal usage in forward.tsx - Add BatchOperationFailure type import - Add BatchActionResultModal component import - Add BatchResultModalState interface and empty state constant - Add batchResultModal state - Add presentBatchOutcome callback for unified batch operation result handling - Update handleBatchDelete to use presentBatchOutcome - Add BatchActionResultModal rendering at end of component --- plans/039-restore-removed-features-pr322.md | 4 +- vite-frontend/src/pages/forward.tsx | 757 ++++++++++++++---- .../src/pages/forward/batch-actions.ts | 2 +- 3 files changed, 583 insertions(+), 180 deletions(-) diff --git a/plans/039-restore-removed-features-pr322.md b/plans/039-restore-removed-features-pr322.md index 82eecec..ebd1ee2 100644 --- a/plans/039-restore-removed-features-pr322.md +++ b/plans/039-restore-removed-features-pr322.md @@ -22,6 +22,7 @@ PR #322 (https://github.com/Sagit-chu/flvx/pull/322) 原本移除了三个功能 - [x] 恢复 `config.tsx` 中的更新通道选择功能 - [x] 恢复 `use-dashboard-data.ts` 中的 `expiryReminderDismissed` 过滤逻辑 - [x] 恢复 `batch-actions.ts` 中的 `BatchOperationFailure` 相关处理 +- [x] 恢复 `forward.tsx` 中的 `BatchActionResultModal` 使用 - [x] 提交并推送修改 ## 修改的文件 @@ -32,8 +33,9 @@ PR #322 (https://github.com/Sagit-chu/flvx/pull/322) 原本移除了三个功能 - `vite-frontend/src/pages/config.tsx` - 添加更新通道选择功能 - `vite-frontend/src/pages/dashboard/use-dashboard-data.ts` - 恢复 `expiryReminderDismissed` 过滤逻辑 - `vite-frontend/src/pages/forward/batch-actions.ts` - 恢复批量操作失败处理 +- `vite-frontend/src/pages/forward.tsx` - 恢复 `BatchActionResultModal` 组件使用 ## 注意事项 - `version-footer.tsx` 保持简化版本显示(不恢复) -- `batch-action-result-modal.tsx` 未被 PR 修改,无需恢复 \ No newline at end of file +- `batch-action-result-modal.tsx` 组件文件未被 PR 删除,无需恢复(只需恢复 forward.tsx 中的使用) \ No newline at end of file diff --git a/vite-frontend/src/pages/forward.tsx b/vite-frontend/src/pages/forward.tsx index 6c96cf4..3f50a76 100644 --- a/vite-frontend/src/pages/forward.tsx +++ b/vite-frontend/src/pages/forward.tsx @@ -1,4 +1,8 @@ -import type { ForwardApiItem, SpeedLimitApiItem } from "@/api/types"; +import type { + BatchOperationFailure, + ForwardApiItem, + SpeedLimitApiItem, +} from "@/api/types"; import { useState, useEffect, useMemo, useRef, useCallback } from "react"; import toast from "react-hot-toast"; @@ -22,8 +26,8 @@ import { import { useSortable } from "@dnd-kit/sortable"; import { CSS } from "@dnd-kit/utilities"; -import { SearchBar } from "@/components/search-bar"; import { AnimatedPage } from "@/components/animated-page"; +import { BatchActionResultModal } from "@/components/batch-action-result-modal"; import { Card, CardBody, CardHeader } from "@/shadcn-bridge/heroui/card"; import { Button } from "@/shadcn-bridge/heroui/button"; import { Input } from "@/shadcn-bridge/heroui/input"; @@ -95,7 +99,6 @@ import { import { buildForwardOrder, FORWARD_ORDER_KEY } from "@/pages/forward/order"; import { PageLoadingState } from "@/components/page-state"; // import { useMobileBreakpoint } from "@/hooks/useMobileBreakpoint"; -import { useLocalStorageState } from "@/hooks/use-local-storage-state"; import { saveOrder } from "@/utils/order-storage"; import { JwtUtil } from "@/utils/jwt"; @@ -175,6 +178,20 @@ interface BatchProgressState { percent: number; } +interface BatchResultModalState { + failures: BatchOperationFailure[]; + open: boolean; + summary: string; + title: string; +} + +const EMPTY_BATCH_RESULT_MODAL_STATE: BatchResultModalState = { + failures: [], + open: false, + summary: "", + title: "", +}; + type ForwardGroupOrderMap = Record; type ForwardGroupCollapsedMap = Record; @@ -583,10 +600,7 @@ const SortableTunnelGroupContainer = ({ bodyClassName: string; children: React.ReactNode; }) => { - const sortableId = buildTunnelGroupSortableId( - groupUserId, - tunnel.tunnelKey, - ); + const sortableId = buildTunnelGroupSortableId(groupUserId, tunnel.tunnelKey); const { attributes, listeners, @@ -599,10 +613,10 @@ const SortableTunnelGroupContainer = ({ const style: React.CSSProperties = { transform: transform ? CSS.Transform.toString({ - ...transform, - x: Math.round(transform.x), - y: Math.round(transform.y), - }) + ...transform, + x: Math.round(transform.x), + y: Math.round(transform.y), + }) : undefined, transition: isDragging ? undefined : transition || undefined, opacity: isDragging ? 0.55 : 1, @@ -679,10 +693,10 @@ const SortableForwardCard = ({ forward, renderCard }: any) => { const style: React.CSSProperties = { transform: transform ? CSS.Transform.toString({ - ...transform, - x: Math.round(transform.x), - y: Math.round(transform.y), - }) + ...transform, + x: Math.round(transform.x), + y: Math.round(transform.y), + }) : undefined, transition: isDragging ? undefined : transition || undefined, opacity: isDragging ? 0.5 : 1, @@ -724,11 +738,11 @@ const SortableTableRow = ({ const style = { transform: CSS.Transform.toString(transform), - transition: isDragging ? 'none' : transition, + transition: isDragging ? "none" : transition, opacity: isDragging ? 0.6 : 1, zIndex: isDragging ? 50 : undefined, - position: isDragging ? ('relative' as const) : undefined, - willChange: 'transform', + position: isDragging ? ("relative" as const) : undefined, + willChange: "transform", backgroundColor: isDragging ? "var(--nextui-default-100)" : undefined, }; @@ -762,17 +776,28 @@ const SortableTableRow = ({ copyToClipboard(forward.name, "规则名")}>{forward.name} + className={`${selectedIds.has(forward.id) ? "bg-primary-50/70 dark:bg-primary-900/40" : ""} ${FORWARD_GROUPED_TABLE_COLUMN_CLASS.name} whitespace-nowrap text-foreground cursor-pointer hover:text-primary transition-colors`} + onClick={() => copyToClipboard(forward.name, "规则名")} + > + {forward.name}
{ e.stopPropagation(); copyToClipboard((forward.inIp || "").replace(/:\d+$/, "") || "默认IP", "入口地址"); }} + }`} + title="点击复制" + onClick={(e) => { + e.stopPropagation(); + copyToClipboard( + (forward.inIp || "").replace(/:\d+$/, "") || "默认IP", + "入口地址", + ); + }} > {formatInAddress(forward.inIp, forward.inPort)}
@@ -781,11 +806,19 @@ const SortableTableRow = ({ className={`${FORWARD_GROUPED_TABLE_COLUMN_CLASS.target} max-w-[280px] ${selectedIds.has(forward.id) ? "bg-primary-50/70 dark:bg-primary-900/40" : ""}`} >
{ e.stopPropagation(); copyToClipboard(forward.remoteAddr.split(",")[0].split(":")[0], "落地地址"); }} + }`} + title="点击复制" + onClick={(e) => { + e.stopPropagation(); + copyToClipboard( + forward.remoteAddr.split(",")[0].split(":")[0], + "落地地址", + ); + }} > {formatRemoteAddress(forward.remoteAddr)}
@@ -807,7 +840,10 @@ const SortableTableRow = ({ {formatFlow(getForwardDisplayFlow(forward))}
- copyToClipboard(forward.inPort.toString(), "入口端口")}> + copyToClipboard(forward.inPort.toString(), "入口端口")} + >
{true && ( - - toggleSelect(forward.id)} /> + + toggleSelect(forward.id)} + /> )} - -
- + +
+
- copyToClipboard(forward.name, "规则名")}>{forward.name} - + + copyToClipboard(forward.name, "规则名")} + > + {forward.name} + + +
- {forward.tunnelName} - ‾{formatTunnelTrafficRatio(forward.tunnelTrafficRatio)} + + {forward.tunnelName} + + + ‾{formatTunnelTrafficRatio(forward.tunnelTrafficRatio)} +
- copyToClipboard((forward.inIp || "").replace(/:\d+$/, "") || "默认IP", "入口地址")}>{(forward.inIp || "").replace(/:\d+$/, "") || "默认IP"} - copyToClipboard(forward.inPort.toString(), "入口端口")}>{forward.inPort} + + + copyToClipboard( + (forward.inIp || "").replace(/:\d+$/, "") || "默认IP", + "入口地址", + ) + } + > + {(forward.inIp || "").replace(/:\d+$/, "") || "默认IP"} + + + + copyToClipboard(forward.inPort.toString(), "入口端口")} + > + {forward.inPort} + + {/* 入口端口点击复制 */} - copyToClipboard(forward.remoteAddr.split(',')[0].replace(/:\d+$/, ""), "落地地址")}>{forward.remoteAddr.split(',')[0].replace(/:\d+$/, "")}{forward.remoteAddr.includes(',') && ...} - copyToClipboard((forward.remoteAddr.split(',')[0].match(/:(\d+)$/)?.[1]) || "", "落地端口")}>{(forward.remoteAddr.split(',')[0].match(/:(\d+)$/)?.[1]) || "-"} - - {strategyDisplay.text} - {formatFlow(getForwardDisplayFlow(forward))} -
handleServiceToggle(forward)} />
- + + + copyToClipboard( + forward.remoteAddr.split(",")[0].replace(/:\d+$/, ""), + "落地地址", + ) + } + > + {forward.remoteAddr.split(",")[0].replace(/:\d+$/, "")} + + {forward.remoteAddr.includes(",") && ( + ... + )} + + + + copyToClipboard( + forward.remoteAddr.split(",")[0].match(/:(\d+)$/)?.[1] || "", + "落地端口", + ) + } + > + {forward.remoteAddr.split(",")[0].match(/:(\d+)$/)?.[1] || "-"} + + + + + + {strategyDisplay.text} + + + + + {formatFlow(getForwardDisplayFlow(forward))} + + + +
+ handleServiceToggle(forward)} + /> +
+
+
- - - + + +
); }; - - const getForwardDisplayFlow = (forward: Forward): number => { const directFlow = (forward.inFlow || 0) + (forward.outFlow || 0); @@ -987,10 +1190,18 @@ export default function ForwardPage() { userId: tokenUserId ? tokenUserId.toString() : "all", tunnelId: "all", inPort: "", - remoteAddr: "" + remoteAddr: "", }); const [isSearchModalOpen, setIsSearchModalOpen] = useState(false); - const activeFilterCount = (searchParams.name ? 1 : 0) + (searchParams.userId !== "all" && searchParams.userId !== (tokenUserId ? tokenUserId.toString() : "all") ? 1 : 0) + (searchParams.tunnelId !== "all" ? 1 : 0) + (searchParams.inPort ? 1 : 0) + (searchParams.remoteAddr ? 1 : 0); + const activeFilterCount = + (searchParams.name ? 1 : 0) + + (searchParams.userId !== "all" && + searchParams.userId !== (tokenUserId ? tokenUserId.toString() : "all") + ? 1 + : 0) + + (searchParams.tunnelId !== "all" ? 1 : 0) + + (searchParams.inPort ? 1 : 0) + + (searchParams.remoteAddr ? 1 : 0); const [loading, setLoading] = useState(true); const [forwards, setForwards] = useState([]); const [tunnels, setTunnels] = useState([]); @@ -1103,6 +1314,8 @@ export default function ForwardPage() { label: "", percent: 0, }); + const [batchResultModal, setBatchResultModal] = + useState(EMPTY_BATCH_RESULT_MODAL_STATE); const [groupOrderMap, setGroupOrderMap] = useState({}); const [collapsedTunnelGroups, setCollapsedTunnelGroups] = useState({}); @@ -1222,7 +1435,7 @@ export default function ForwardPage() { buildForwardGroupOrderLocalKey(tokenUserId), JSON.stringify(nextOrderMap), ); - } catch { } + } catch {} }; const persistGroupCollapsedToLocal = ( @@ -1237,7 +1450,7 @@ export default function ForwardPage() { buildForwardGroupCollapsedLocalKey(tokenUserId), JSON.stringify(nextCollapsedMap), ); - } catch { } + } catch {} }; const persistGroupOrderToGlobal = async ( @@ -1381,7 +1594,7 @@ export default function ForwardPage() { JSON.stringify(globalCollapsedBucket), ); } - } catch { } + } catch {} } if (cancelled) { @@ -1645,7 +1858,7 @@ export default function ForwardPage() { setViewMode(newMode); try { localStorage.setItem("forward-view-mode", newMode); - } catch { } + } catch {} }; const applyForwardList = useCallback( @@ -2035,10 +2248,10 @@ export default function ForwardPage() { if (res.code === 0) { const warningItems = Array.isArray((res as any).data?.warnings) ? (res as any).data.warnings - .map((item: unknown) => - typeof item === "string" ? item.trim() : "", - ) - .filter((item: string) => item) + .map((item: unknown) => + typeof item === "string" ? item.trim() : "", + ) + .filter((item: string) => item) : []; warningItems.forEach((warning: string) => { @@ -2151,7 +2364,7 @@ export default function ForwardPage() { onStart: (payload) => { const startForwardName = typeof payload.forwardName === "string" && - payload.forwardName.trim() !== "" + payload.forwardName.trim() !== "" ? payload.forwardName : forward.name; const startTotal = Number(payload.total); @@ -2345,17 +2558,25 @@ export default function ForwardPage() { toast.success(`已复制${label}`); } else { const textArea = document.createElement("textarea"); + textArea.value = text; textArea.style.position = "fixed"; textArea.style.left = "-9999px"; textArea.style.top = "0"; document.body.appendChild(textArea); - textArea.focus(); textArea.select(); - try { document.execCommand('copy'); toast.success(`已复制${label}`); } - catch (err) { toast.error("复制失败"); } + textArea.focus(); + textArea.select(); + try { + document.execCommand("copy"); + toast.success(`已复制${label}`); + } catch (err) { + toast.error("复制失败"); + } document.body.removeChild(textArea); } - } catch { toast.error("复制失败"); } + } catch { + toast.error("复制失败"); + } }; // 复制地址 @@ -2848,6 +3069,36 @@ export default function ForwardPage() { setSelectedIds(new Set()); }; + const presentBatchOutcome = useCallback( + (outcome: { + failureDetails?: BatchOperationFailure[]; + resultSummary?: string; + resultTitle?: string; + toastMessage: string; + toastVariant: "success" | "error"; + }) => { + const failureDetails = outcome.failureDetails || []; + + if (failureDetails.length > 0) { + setBatchResultModal({ + failures: failureDetails, + open: true, + summary: outcome.resultSummary || outcome.toastMessage, + title: outcome.resultTitle || "批量操作结果", + }); + + return; + } + + if (outcome.toastVariant === "success") { + toast.success(outcome.toastMessage); + } else { + toast.error(outcome.toastMessage); + } + }, + [], + ); + const handleBatchDelete = async () => { if (selectedIds.size === 0) return; setBatchLoading(true); @@ -2859,11 +3110,7 @@ export default function ForwardPage() { try { const outcome = await executeForwardBatchDelete(Array.from(selectedIds)); - if (outcome.toastVariant === "success") { - toast.success(outcome.toastMessage); - } else { - toast.error(outcome.toastMessage); - } + presentBatchOutcome(outcome); if (outcome.shouldRefresh) { setBatchProgress({ @@ -3026,25 +3273,40 @@ export default function ForwardPage() { if (searchParams.userId !== "all") { const targetUserId = parseInt(searchParams.userId); - filteredForwards = filteredForwards.filter(f => f.userId === targetUserId || (targetUserId === 0 && !f.userId)); + + filteredForwards = filteredForwards.filter( + (f) => f.userId === targetUserId || (targetUserId === 0 && !f.userId), + ); } if (searchParams.tunnelId !== "all") { const targetTunnelId = parseInt(searchParams.tunnelId); - filteredForwards = filteredForwards.filter(f => f.tunnelId === targetTunnelId); + + filteredForwards = filteredForwards.filter( + (f) => f.tunnelId === targetTunnelId, + ); } if (searchParams.name.trim()) { const lowerName = searchParams.name.toLowerCase(); - filteredForwards = filteredForwards.filter(f => f.name && f.name.toLowerCase().includes(lowerName)); + + filteredForwards = filteredForwards.filter( + (f) => f.name && f.name.toLowerCase().includes(lowerName), + ); } if (searchParams.inPort.trim()) { const targetPort = parseInt(searchParams.inPort.trim()); + if (!isNaN(targetPort)) { - filteredForwards = filteredForwards.filter(f => f.inPort === targetPort); + filteredForwards = filteredForwards.filter( + (f) => f.inPort === targetPort, + ); } } if (searchParams.remoteAddr.trim()) { const lowerAddr = searchParams.remoteAddr.toLowerCase(); - filteredForwards = filteredForwards.filter(f => f.remoteAddr && f.remoteAddr.toLowerCase().includes(lowerAddr)); + + filteredForwards = filteredForwards.filter( + (f) => f.remoteAddr && f.remoteAddr.toLowerCase().includes(lowerAddr), + ); } // 确保过滤后的规则列表有效 @@ -3213,7 +3475,7 @@ export default function ForwardPage() { if ( normalizeTunnelTrafficRatio(existingTunnelGroup.tunnelTrafficRatio) === - 1 && + 1 && normalizeTunnelTrafficRatio(forward.tunnelTrafficRatio) !== 1 ) { existingTunnelGroup.tunnelTrafficRatio = normalizeTunnelTrafficRatio( @@ -3303,31 +3565,38 @@ export default function ForwardPage() { [sortedForwards], ); - const selectAll = () => { const allIds = sortedForwards.map((f) => f.id); + setSelectedIds(new Set(allIds)); }; const isAllSelected = useMemo(() => { - return sortedForwards && sortedForwards.length > 0 && selectedIds.size === sortedForwards.length; + return ( + sortedForwards && + sortedForwards.length > 0 && + selectedIds.size === sortedForwards.length + ); }, [sortedForwards, selectedIds]); const isIndeterminate = useMemo(() => { - return selectedIds.size > 0 && sortedForwards && selectedIds.size < sortedForwards.length; + return ( + selectedIds.size > 0 && + sortedForwards && + selectedIds.size < sortedForwards.length + ); }, [sortedForwards, selectedIds]); const handleSelectAllToggle = (isSelected: boolean) => { if (isSelected) { const allIds = sortedForwards.map((f) => f.id); + setSelectedIds(new Set(allIds)); } else { setSelectedIds(new Set()); } }; - - const toggleTunnelGroupCollapsed = (userId: number, tunnelKey: string) => { const collapseKey = buildTunnelGroupCollapseKey(userId, tunnelKey); const nextCollapsedMap: ForwardGroupCollapsedMap = { @@ -3417,14 +3686,15 @@ export default function ForwardPage() {
{/* 倍率 */} - {normalizeForwardTunnelName(forward.tunnelName)} + + {normalizeForwardTunnelName(forward.tunnelName)} + ‾{formatTunnelTrafficRatio(forward.tunnelTrafficRatio)}
- {statusDisplay.text} - {viewMode === "direct" && (
)} + {viewMode === "direct" && ( +
+ +
+ )}
@@ -3449,10 +3735,11 @@ export default function ForwardPage() { {/* 地址信息 */}
{activeFilterCount > 0 && ( - )} @@ -3756,7 +4066,6 @@ export default function ForwardPage() { > 隧道 - ) : ( <> @@ -3821,8 +4130,6 @@ export default function ForwardPage() { 导出 - -
@@ -4039,10 +4369,10 @@ export default function ForwardPage() { .filter((id) => id > 0); const collapsed = sanitizedCollapsedTunnelGroups[ - buildTunnelGroupCollapseKey( - group.userId, - tunnel.tunnelKey, - ) + buildTunnelGroupCollapseKey( + group.userId, + tunnel.tunnelKey, + ) ] === true; return ( @@ -4081,7 +4411,12 @@ export default function ForwardPage() { {true && ( {/* @ts-ignore */} - + )} - id > 0); const collapsed = sanitizedCollapsedTunnelGroups[ - buildTunnelGroupCollapseKey( - group.userId, - tunnel.tunnelKey, - ) + buildTunnelGroupCollapseKey( + group.userId, + tunnel.tunnelKey, + ) ] === true; return ( @@ -4280,7 +4616,7 @@ export default function ForwardPage() { collisionDetection={pointerWithin} sensors={sensors} onDragEnd={handleDragEnd} - onDragStart={() => { }} + onDragStart={() => {}} > (
{result.success ? ( @@ -4942,10 +5289,11 @@ export default function ForwardPage() {
{result.success ? "成功" : "失败"} @@ -4957,10 +5305,11 @@ export default function ForwardPage() {
{result.message}
@@ -4992,7 +5341,9 @@ export default function ForwardPage() { {/* 诊断结果模态框 */}
{diagnosisProgress.completed > 0 || - diagnosisProgress.total > 0 + diagnosisProgress.total > 0 ? diagnosisProgress.success : diagnosisResult.results.filter((r) => r.success) - .length} + .length}
成功 @@ -5083,10 +5434,10 @@ export default function ForwardPage() {
{diagnosisProgress.completed > 0 || - diagnosisProgress.total > 0 + diagnosisProgress.total > 0 ? diagnosisProgress.failed : diagnosisResult.results.filter((r) => !r.success) - .length} + .length}
失败 @@ -5172,12 +5523,13 @@ export default function ForwardPage() { return (
@@ -5185,10 +5537,11 @@ export default function ForwardPage() { ) : ( {isSuccess ? "✓" : "✗"} @@ -5237,10 +5590,11 @@ export default function ForwardPage() { {isSuccess ? ( 0 + className={`font-semibold ${ + (result.packetLoss || 0) > 0 ? "text-warning" : "text-success" - }`} + }`} > {result.packetLoss?.toFixed(1)}% @@ -5356,22 +5710,24 @@ export default function ForwardPage() { return (
{isDiagnosing ? ( ) : ( {isSuccess ? "✓" : "✗"} @@ -5416,10 +5772,11 @@ export default function ForwardPage() {
0 + className={`text-lg font-bold ${ + (result.packetLoss || 0) > 0 ? "text-warning" : "text-success" - }`} + }`} > {result.packetLoss?.toFixed(1)}%
@@ -5448,10 +5805,11 @@ export default function ForwardPage() { ) : (
{isDiagnosing ? result.message || "诊断中..." @@ -5499,26 +5857,26 @@ export default function ForwardPage() { {diagnosisResult.results.some( (r) => r.success === false && !r.diagnosing, ) && ( -
-

- 失败详情 -

-
- {diagnosisResult.results - .filter((r) => r.success === false && !r.diagnosing) - .map((result, index) => ( - - ))} -
+
+

+ 失败详情 +

+
+ {diagnosisResult.results + .filter((r) => r.success === false && !r.diagnosing) + .map((result, index) => ( + + ))}
- )} +
+ )}
) : (
@@ -5565,7 +5923,9 @@ export default function ForwardPage() { {/* 批量删除确认模态框 */} @@ -5597,7 +5957,9 @@ export default function ForwardPage() { {/* 批量换隧道模态框 */} @@ -5646,10 +6008,11 @@ export default function ForwardPage() { - {/* 搜索与筛选五合一模态框 */} setSearchParams(prev => ({ ...prev, name: e.target.value }))} variant="bordered" + onChange={(e) => + setSearchParams((prev) => ({ + ...prev, + name: e.target.value, + })) + } /> {isAdmin && ( @@ -5679,7 +6047,11 @@ export default function ForwardPage() { variant="bordered" onSelectionChange={(keys) => { const key = Array.from(keys)[0] as string; - setSearchParams(prev => ({ ...prev, userId: key || "all" })); + + setSearchParams((prev) => ({ + ...prev, + userId: key || "all", + })); }} > 全部用户 @@ -5698,7 +6070,11 @@ export default function ForwardPage() { variant="bordered" onSelectionChange={(keys) => { const key = Array.from(keys)[0] as string; - setSearchParams(prev => ({ ...prev, tunnelId: key || "all" })); + + setSearchParams((prev) => ({ + ...prev, + tunnelId: key || "all", + })); }} > 全部隧道 @@ -5714,16 +6090,26 @@ export default function ForwardPage() { placeholder="请输入具体端口号" type="number" value={searchParams.inPort} - onChange={(e) => setSearchParams(prev => ({ ...prev, inPort: e.target.value }))} variant="bordered" + onChange={(e) => + setSearchParams((prev) => ({ + ...prev, + inPort: e.target.value, + })) + } /> setSearchParams(prev => ({ ...prev, remoteAddr: e.target.value }))} variant="bordered" + onChange={(e) => + setSearchParams((prev) => ({ + ...prev, + remoteAddr: e.target.value, + })) + } />
@@ -5737,7 +6123,7 @@ export default function ForwardPage() { userId: tokenUserId ? tokenUserId.toString() : "all", tunnelId: "all", inPort: "", - remoteAddr: "" + remoteAddr: "", }); }} > @@ -5749,6 +6135,21 @@ export default function ForwardPage() { + { + if (open) { + setBatchResultModal((prev) => ({ ...prev, open: true })); + + return; + } + + setBatchResultModal(EMPTY_BATCH_RESULT_MODAL_STATE); + }} + /> ); } diff --git a/vite-frontend/src/pages/forward/batch-actions.ts b/vite-frontend/src/pages/forward/batch-actions.ts index 8647846..e375798 100644 --- a/vite-frontend/src/pages/forward/batch-actions.ts +++ b/vite-frontend/src/pages/forward/batch-actions.ts @@ -223,4 +223,4 @@ export const executeForwardBatchChangeTunnel = async ( shouldRefresh: false, }; } -}; \ No newline at end of file +};