mirror of
https://github.com/Sagit-chu/flvx.git
synced 2026-10-03 17:06:36 +08:00
Restore BatchActionResultModal usage in tunnel.tsx
This commit is contained in:
@@ -23,7 +23,8 @@ PR #322 (https://github.com/Sagit-chu/flvx/pull/322) 原本移除了三个功能
|
|||||||
- [x] 恢复 `use-dashboard-data.ts` 中的 `expiryReminderDismissed` 过滤逻辑
|
- [x] 恢复 `use-dashboard-data.ts` 中的 `expiryReminderDismissed` 过滤逻辑
|
||||||
- [x] 恢复 `batch-actions.ts` 中的 `BatchOperationFailure` 相关处理
|
- [x] 恢复 `batch-actions.ts` 中的 `BatchOperationFailure` 相关处理
|
||||||
- [x] 恢复 `forward.tsx` 中的 `BatchActionResultModal` 使用
|
- [x] 恢复 `forward.tsx` 中的 `BatchActionResultModal` 使用
|
||||||
- [x] 提交并推送修改
|
- [x] 恢复 `tunnel.tsx` 中的 `BatchActionResultModal` 使用
|
||||||
|
- [ ] 提交并推送修改
|
||||||
|
|
||||||
## 修改的文件
|
## 修改的文件
|
||||||
|
|
||||||
@@ -34,6 +35,7 @@ PR #322 (https://github.com/Sagit-chu/flvx/pull/322) 原本移除了三个功能
|
|||||||
- `vite-frontend/src/pages/dashboard/use-dashboard-data.ts` - 恢复 `expiryReminderDismissed` 过滤逻辑
|
- `vite-frontend/src/pages/dashboard/use-dashboard-data.ts` - 恢复 `expiryReminderDismissed` 过滤逻辑
|
||||||
- `vite-frontend/src/pages/forward/batch-actions.ts` - 恢复批量操作失败处理
|
- `vite-frontend/src/pages/forward/batch-actions.ts` - 恢复批量操作失败处理
|
||||||
- `vite-frontend/src/pages/forward.tsx` - 恢复 `BatchActionResultModal` 组件使用
|
- `vite-frontend/src/pages/forward.tsx` - 恢复 `BatchActionResultModal` 组件使用
|
||||||
|
- `vite-frontend/src/pages/tunnel.tsx` - 恢复 `BatchActionResultModal` 组件使用
|
||||||
|
|
||||||
## 注意事项
|
## 注意事项
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import type { BatchOperationFailure } from "@/api/types";
|
||||||
|
|
||||||
import { useState, useEffect, useMemo, useRef, useCallback } from "react";
|
import { useState, useEffect, useMemo, useRef, useCallback } from "react";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
import {
|
import {
|
||||||
@@ -20,6 +22,7 @@ import { CSS } from "@dnd-kit/utilities";
|
|||||||
|
|
||||||
import { SearchBar } from "@/components/search-bar";
|
import { SearchBar } from "@/components/search-bar";
|
||||||
import { AnimatedPage } from "@/components/animated-page";
|
import { AnimatedPage } from "@/components/animated-page";
|
||||||
|
import { BatchActionResultModal } from "@/components/batch-action-result-modal";
|
||||||
import { Card, CardBody, CardHeader } from "@/shadcn-bridge/heroui/card";
|
import { Card, CardBody, CardHeader } from "@/shadcn-bridge/heroui/card";
|
||||||
import { Button } from "@/shadcn-bridge/heroui/button";
|
import { Button } from "@/shadcn-bridge/heroui/button";
|
||||||
import { Input, Textarea } from "@/shadcn-bridge/heroui/input";
|
import { Input, Textarea } from "@/shadcn-bridge/heroui/input";
|
||||||
@@ -63,7 +66,11 @@ import {
|
|||||||
} from "@/pages/tunnel/form";
|
} from "@/pages/tunnel/form";
|
||||||
import { useLocalStorageState } from "@/hooks/use-local-storage-state";
|
import { useLocalStorageState } from "@/hooks/use-local-storage-state";
|
||||||
import { loadStoredOrder, saveOrder } from "@/utils/order-storage";
|
import { loadStoredOrder, saveOrder } from "@/utils/order-storage";
|
||||||
import { extractApiErrorMessage } from "@/api/error-message";
|
import {
|
||||||
|
buildBatchFailureMessage,
|
||||||
|
extractBatchFailures,
|
||||||
|
extractApiErrorMessage,
|
||||||
|
} from "@/api/error-message";
|
||||||
|
|
||||||
interface ChainTunnel {
|
interface ChainTunnel {
|
||||||
nodeId: number;
|
nodeId: number;
|
||||||
@@ -122,6 +129,20 @@ interface BatchProgressState {
|
|||||||
percent: number;
|
percent: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface BatchResultModalState {
|
||||||
|
failures: BatchOperationFailure[];
|
||||||
|
open: boolean;
|
||||||
|
summary: string;
|
||||||
|
title: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const EMPTY_BATCH_RESULT_MODAL_STATE: BatchResultModalState = {
|
||||||
|
failures: [],
|
||||||
|
open: false,
|
||||||
|
summary: "",
|
||||||
|
title: "",
|
||||||
|
};
|
||||||
|
|
||||||
const TUNNEL_ORDER_KEY = "tunnel-order";
|
const TUNNEL_ORDER_KEY = "tunnel-order";
|
||||||
|
|
||||||
const mapTunnelApiItems = (items: any[]): Tunnel[] => {
|
const mapTunnelApiItems = (items: any[]): Tunnel[] => {
|
||||||
@@ -232,6 +253,8 @@ export default function TunnelPage() {
|
|||||||
label: "",
|
label: "",
|
||||||
percent: 0,
|
percent: 0,
|
||||||
});
|
});
|
||||||
|
const [batchResultModal, setBatchResultModal] =
|
||||||
|
useState<BatchResultModalState>(EMPTY_BATCH_RESULT_MODAL_STATE);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
@@ -358,7 +381,7 @@ export default function TunnelPage() {
|
|||||||
});
|
});
|
||||||
setErrors({});
|
setErrors({});
|
||||||
setModalOpen(true);
|
setModalOpen(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 删除隧道
|
// 删除隧道
|
||||||
const handleDelete = (tunnel: Tunnel) => {
|
const handleDelete = (tunnel: Tunnel) => {
|
||||||
@@ -842,6 +865,18 @@ export default function TunnelPage() {
|
|||||||
setSelectedIds(new Set());
|
setSelectedIds(new Set());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const openBatchResultModal = useCallback(
|
||||||
|
(title: string, summary: string, failures: BatchOperationFailure[]) => {
|
||||||
|
setBatchResultModal({
|
||||||
|
failures,
|
||||||
|
open: true,
|
||||||
|
summary,
|
||||||
|
title,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
const handleBatchDelete = async () => {
|
const handleBatchDelete = async () => {
|
||||||
if (selectedIds.size === 0) return;
|
if (selectedIds.size === 0) return;
|
||||||
setBatchLoading(true);
|
setBatchLoading(true);
|
||||||
@@ -874,9 +909,19 @@ export default function TunnelPage() {
|
|||||||
return next;
|
return next;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
toast.error(
|
const failures = extractBatchFailures(result);
|
||||||
`成功 ${result.successCount} 项,失败 ${result.failCount} 项`,
|
|
||||||
);
|
if (failures.length > 0) {
|
||||||
|
openBatchResultModal(
|
||||||
|
"批量删除结果",
|
||||||
|
`成功 ${result.successCount} 项,失败 ${result.failCount} 项`,
|
||||||
|
failures,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
toast.error(
|
||||||
|
`成功 ${result.successCount} 项,失败 ${result.failCount} 项`,
|
||||||
|
);
|
||||||
|
}
|
||||||
setBatchProgress({
|
setBatchProgress({
|
||||||
active: true,
|
active: true,
|
||||||
label: `部分完成:成功 ${result.successCount} 项,正在刷新列表...`,
|
label: `部分完成:成功 ${result.successCount} 项,正在刷新列表...`,
|
||||||
@@ -915,9 +960,22 @@ export default function TunnelPage() {
|
|||||||
if (result.failCount === 0) {
|
if (result.failCount === 0) {
|
||||||
toast.success(`成功重新下发 ${result.successCount} 项`);
|
toast.success(`成功重新下发 ${result.successCount} 项`);
|
||||||
} else {
|
} else {
|
||||||
toast.error(
|
const failures = extractBatchFailures(result);
|
||||||
`成功 ${result.successCount} 项,失败 ${result.failCount} 项`,
|
|
||||||
);
|
if (failures.length > 0) {
|
||||||
|
openBatchResultModal(
|
||||||
|
"批量下发结果",
|
||||||
|
`成功 ${result.successCount} 项,失败 ${result.failCount} 项`,
|
||||||
|
failures,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
toast.error(
|
||||||
|
buildBatchFailureMessage(
|
||||||
|
result,
|
||||||
|
`成功 ${result.successCount} 项,失败 ${result.failCount} 项`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
setSelectedIds(new Set());
|
setSelectedIds(new Set());
|
||||||
setSelectMode(false);
|
setSelectMode(false);
|
||||||
@@ -1204,7 +1262,21 @@ export default function TunnelPage() {
|
|||||||
</Chip>
|
</Chip>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="cursor-grab active:cursor-grabbing p-1 -mr-1 text-default-400 hover:text-default-600 transition-colors touch-manipulation flex-shrink-0" {...listeners} style={{ touchAction: "none" }} title="拖拽排序"><svg aria-hidden="true" className="w-4 h-4" fill="currentColor" viewBox="0 0 20 20"><path d="M7 2a2 2 0 1 1 .001 4.001A2 2 0 0 1 7 2zm0 6a2 2 0 1 1 .001 4.001A2 2 0 0 1 7 8zm0 6a2 2 0 1 1 .001 4.001A2 2 0 0 1 7 14zm6-8a2 2 0 1 1-.001-4.001A2 2 0 0 1 13 6zm0 2a2 2 0 1 1 .001 4.001A2 2 0 0 1 13 8zm0 6a2 2 0 1 1 .001 4.001A2 2 0 0 1 13 14z" /></svg></div>
|
<div
|
||||||
|
className="cursor-grab active:cursor-grabbing p-1 -mr-1 text-default-400 hover:text-default-600 transition-colors touch-manipulation flex-shrink-0"
|
||||||
|
{...listeners}
|
||||||
|
style={{ touchAction: "none" }}
|
||||||
|
title="拖拽排序"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className="w-4 h-4"
|
||||||
|
fill="currentColor"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
>
|
||||||
|
<path d="M7 2a2 2 0 1 1 .001 4.001A2 2 0 0 1 7 2zm0 6a2 2 0 1 1 .001 4.001A2 2 0 0 1 7 8zm0 6a2 2 0 1 1 .001 4.001A2 2 0 0 1 7 14zm6-8a2 2 0 1 1-.001-4.001A2 2 0 0 1 13 6zm0 2a2 2 0 1 1 .001 4.001A2 2 0 0 1 13 8zm0 6a2 2 0 1 1 .001 4.001A2 2 0 0 1 13 14z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|
||||||
@@ -1343,7 +1415,6 @@ export default function TunnelPage() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex gap-1.5 mt-3">
|
<div className="flex gap-1.5 mt-3">
|
||||||
@@ -1442,8 +1513,11 @@ export default function TunnelPage() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 新增/编辑模态框 */}
|
{/* 新增/编辑模态框 */}
|
||||||
<Modal classNames={{ base: "!w-[calc(100%-32px)] !mx-auto sm:!w-full rounded-2xl overflow-hidden" }}
|
<Modal
|
||||||
backdrop="blur"
|
backdrop="blur"
|
||||||
|
classNames={{
|
||||||
|
base: "!w-[calc(100%-32px)] !mx-auto sm:!w-full rounded-2xl overflow-hidden",
|
||||||
|
}}
|
||||||
isOpen={modalOpen}
|
isOpen={modalOpen}
|
||||||
placement="center"
|
placement="center"
|
||||||
scrollBehavior="outside"
|
scrollBehavior="outside"
|
||||||
@@ -2376,8 +2450,11 @@ export default function TunnelPage() {
|
|||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
{/* 删除确认模态框 */}
|
{/* 删除确认模态框 */}
|
||||||
<Modal classNames={{ base: "!w-[calc(100%-32px)] !mx-auto sm:!w-full rounded-2xl overflow-hidden" }}
|
<Modal
|
||||||
backdrop="blur"
|
backdrop="blur"
|
||||||
|
classNames={{
|
||||||
|
base: "!w-[calc(100%-32px)] !mx-auto sm:!w-full rounded-2xl overflow-hidden",
|
||||||
|
}}
|
||||||
isOpen={deleteModalOpen}
|
isOpen={deleteModalOpen}
|
||||||
placement="center"
|
placement="center"
|
||||||
scrollBehavior="outside"
|
scrollBehavior="outside"
|
||||||
@@ -2419,7 +2496,9 @@ export default function TunnelPage() {
|
|||||||
{/* 诊断结果模态框 */}
|
{/* 诊断结果模态框 */}
|
||||||
<Modal
|
<Modal
|
||||||
backdrop="blur"
|
backdrop="blur"
|
||||||
classNames={{ base: "!w-[calc(100%-32px)] !mx-auto sm:!w-full rounded-2xl overflow-hidden" }}
|
classNames={{
|
||||||
|
base: "!w-[calc(100%-32px)] !mx-auto sm:!w-full rounded-2xl overflow-hidden",
|
||||||
|
}}
|
||||||
isOpen={diagnosisModalOpen}
|
isOpen={diagnosisModalOpen}
|
||||||
placement="center"
|
placement="center"
|
||||||
scrollBehavior="inside"
|
scrollBehavior="inside"
|
||||||
@@ -3000,7 +3079,11 @@ export default function TunnelPage() {
|
|||||||
</ModalContent>
|
</ModalContent>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<Modal backdrop="blur" classNames={{ base: "!w-[calc(100%-32px)] !mx-auto sm:!w-full rounded-2xl overflow-hidden" }}
|
<Modal
|
||||||
|
backdrop="blur"
|
||||||
|
classNames={{
|
||||||
|
base: "!w-[calc(100%-32px)] !mx-auto sm:!w-full rounded-2xl overflow-hidden",
|
||||||
|
}}
|
||||||
isOpen={batchDeleteModalOpen}
|
isOpen={batchDeleteModalOpen}
|
||||||
onOpenChange={setBatchDeleteModalOpen}
|
onOpenChange={setBatchDeleteModalOpen}
|
||||||
>
|
>
|
||||||
@@ -3030,6 +3113,20 @@ export default function TunnelPage() {
|
|||||||
)}
|
)}
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
|
<BatchActionResultModal
|
||||||
|
failures={batchResultModal.failures}
|
||||||
|
isOpen={batchResultModal.open}
|
||||||
|
summary={batchResultModal.summary}
|
||||||
|
title={batchResultModal.title}
|
||||||
|
onOpenChange={(open) => {
|
||||||
|
if (open) {
|
||||||
|
setBatchResultModal((prev) => ({ ...prev, open: true }));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setBatchResultModal(EMPTY_BATCH_RESULT_MODAL_STATE);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</AnimatedPage>
|
</AnimatedPage>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user