mirror of
https://github.com/Sagit-chu/flvx.git
synced 2026-10-06 10:06:36 +08:00
feat: add batch pause/resume controls and improve batch toolbar usability
This commit is contained in:
@@ -131,6 +131,10 @@ export const verifyCaptcha = (data: { captchaId: string; trackData: string }) =>
|
||||
// 批量操作接口
|
||||
export const batchDeleteForwards = (ids: number[]) =>
|
||||
Network.post("/forward/batch-delete", { ids });
|
||||
export const batchPauseForwards = (ids: number[]) =>
|
||||
Network.post("/forward/batch-pause", { ids });
|
||||
export const batchResumeForwards = (ids: number[]) =>
|
||||
Network.post("/forward/batch-resume", { ids });
|
||||
export const batchDeleteTunnels = (ids: number[]) =>
|
||||
Network.post("/tunnel/batch-delete", { ids });
|
||||
export const batchDeleteNodes = (ids: number[]) =>
|
||||
|
||||
@@ -62,7 +62,7 @@ export default function AdminLayout({
|
||||
const menuItems: MenuItem[] = [
|
||||
{
|
||||
path: "/dashboard",
|
||||
label: "仪表板",
|
||||
label: "仪表",
|
||||
icon: (
|
||||
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M3 4a1 1 0 011-1h12a1 1 0 011 1v2a1 1 0 01-1 1H4a1 1 0 01-1-1V4zM3 10a1 1 0 011-1h6a1 1 0 011 1v6a1 1 0 01-1 1H4a1 1 0 01-1-1v-6zM14 9a1 1 0 00-1 1v6a1 1 0 001 1h2a1 1 0 001-1v-6a1 1 0 00-1-1h-2z" />
|
||||
@@ -71,7 +71,7 @@ export default function AdminLayout({
|
||||
},
|
||||
{
|
||||
path: "/forward",
|
||||
label: "转发管理",
|
||||
label: "转发",
|
||||
icon: (
|
||||
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
@@ -84,7 +84,7 @@ export default function AdminLayout({
|
||||
},
|
||||
{
|
||||
path: "/tunnel",
|
||||
label: "隧道管理",
|
||||
label: "隧道",
|
||||
icon: (
|
||||
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
@@ -98,7 +98,7 @@ export default function AdminLayout({
|
||||
},
|
||||
{
|
||||
path: "/node",
|
||||
label: "节点监控",
|
||||
label: "节点",
|
||||
icon: (
|
||||
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
@@ -112,7 +112,7 @@ export default function AdminLayout({
|
||||
},
|
||||
{
|
||||
path: "/limit",
|
||||
label: "限速管理",
|
||||
label: "限速",
|
||||
icon: (
|
||||
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
@@ -126,7 +126,7 @@ export default function AdminLayout({
|
||||
},
|
||||
{
|
||||
path: "/user",
|
||||
label: "用户管理",
|
||||
label: "用户",
|
||||
icon: (
|
||||
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M9 6a3 3 0 11-6 0 3 3 0 016 0zM17 6a3 3 0 11-6 0 3 3 0 016 0zM12.93 17c.046-.327.07-.66.07-1a6.97 6.97 0 00-1.5-4.33A5 5 0 0119 16v1h-6.07zM6 11a5 5 0 015 5v1H1v-1a5 5 0 015-5z" />
|
||||
@@ -136,7 +136,7 @@ export default function AdminLayout({
|
||||
},
|
||||
{
|
||||
path: "/config",
|
||||
label: "网站配置",
|
||||
label: "设置",
|
||||
icon: (
|
||||
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
|
||||
@@ -49,6 +49,8 @@ import {
|
||||
diagnoseForward,
|
||||
updateForwardOrder,
|
||||
batchDeleteForwards,
|
||||
batchPauseForwards,
|
||||
batchResumeForwards,
|
||||
batchRedeployForwards,
|
||||
batchChangeTunnel,
|
||||
} from "@/api";
|
||||
@@ -1324,6 +1326,38 @@ export default function ForwardPage() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleBatchToggleService = async (enable: boolean) => {
|
||||
if (selectedIds.size === 0) return;
|
||||
setBatchLoading(true);
|
||||
try {
|
||||
const ids = Array.from(selectedIds);
|
||||
const res = enable
|
||||
? await batchResumeForwards(ids)
|
||||
: await batchPauseForwards(ids);
|
||||
if (res.code === 0) {
|
||||
const result = res.data;
|
||||
if (result.failCount === 0) {
|
||||
toast.success(
|
||||
enable
|
||||
? `成功启用 ${result.successCount} 项`
|
||||
: `成功停用 ${result.successCount} 项`,
|
||||
);
|
||||
} else {
|
||||
toast.error(`成功 ${result.successCount} 项,失败 ${result.failCount} 项`);
|
||||
}
|
||||
setSelectedIds(new Set());
|
||||
setSelectMode(false);
|
||||
loadData(false);
|
||||
} else {
|
||||
toast.error(res.msg || (enable ? "启用失败" : "停用失败"));
|
||||
}
|
||||
} catch (e: any) {
|
||||
toast.error(e.message || (enable ? "启用失败" : "停用失败"));
|
||||
} finally {
|
||||
setBatchLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleBatchRedeploy = async () => {
|
||||
if (selectedIds.size === 0) return;
|
||||
setBatchLoading(true);
|
||||
@@ -1731,9 +1765,9 @@ export default function ForwardPage() {
|
||||
return (
|
||||
<div className="px-3 lg:px-6 py-8">
|
||||
{/* 页面头部 */}
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div className="flex items-center justify-between mb-6 gap-2">
|
||||
<div className="flex-1"></div>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex flex-wrap items-center justify-end gap-2 sm:gap-3">
|
||||
{/* 显示模式切换按钮 */}
|
||||
<Button
|
||||
isIconOnly
|
||||
@@ -1796,39 +1830,59 @@ export default function ForwardPage() {
|
||||
</div>
|
||||
|
||||
{selectMode && selectedIds.size > 0 && (
|
||||
<div className="fixed bottom-4 left-1/2 transform -translate-x-1/2 z-50 bg-content1 shadow-lg rounded-lg border border-divider p-3 flex items-center gap-3">
|
||||
<span className="text-sm text-default-600">已选择 {selectedIds.size} 项</span>
|
||||
<Button size="sm" variant="flat" onPress={selectAll}>
|
||||
全选
|
||||
</Button>
|
||||
<Button size="sm" variant="flat" onPress={deselectAll}>
|
||||
清空
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
color="danger"
|
||||
variant="flat"
|
||||
onPress={() => setBatchDeleteModalOpen(true)}
|
||||
>
|
||||
删除
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
color="primary"
|
||||
variant="flat"
|
||||
onPress={handleBatchRedeploy}
|
||||
isLoading={batchLoading}
|
||||
>
|
||||
下发
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
color="secondary"
|
||||
variant="flat"
|
||||
onPress={() => setBatchChangeTunnelModalOpen(true)}
|
||||
>
|
||||
隧道
|
||||
</Button>
|
||||
<div className="fixed bottom-7 left-1/2 z-50 w-[calc(100vw-1rem)] max-w-max -translate-x-1/2 overflow-x-auto rounded-lg border border-divider bg-content1 p-2 shadow-lg">
|
||||
<div className="flex min-w-max items-center gap-2">
|
||||
<span className="text-sm text-default-600 shrink-0">已选择 {selectedIds.size} 项</span>
|
||||
<Button size="sm" variant="flat" onPress={selectAll}>
|
||||
全选
|
||||
</Button>
|
||||
<Button size="sm" variant="flat" onPress={deselectAll}>
|
||||
清空
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
color="danger"
|
||||
variant="flat"
|
||||
onPress={() => setBatchDeleteModalOpen(true)}
|
||||
>
|
||||
删除
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
color="warning"
|
||||
variant="flat"
|
||||
onPress={() => handleBatchToggleService(false)}
|
||||
isLoading={batchLoading}
|
||||
>
|
||||
停用
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
color="success"
|
||||
variant="flat"
|
||||
onPress={() => handleBatchToggleService(true)}
|
||||
isLoading={batchLoading}
|
||||
>
|
||||
启用
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
color="primary"
|
||||
variant="flat"
|
||||
onPress={handleBatchRedeploy}
|
||||
isLoading={batchLoading}
|
||||
>
|
||||
下发
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
color="secondary"
|
||||
variant="flat"
|
||||
onPress={() => setBatchChangeTunnelModalOpen(true)}
|
||||
>
|
||||
隧道
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -1032,11 +1032,11 @@ export default function NodePage() {
|
||||
|
||||
{/* 批量操作浮动工具栏 */}
|
||||
{selectMode && selectedIds.size > 0 && (
|
||||
<div className="fixed bottom-6 left-1/2 -translate-x-1/2 z-50 bg-content1 shadow-lg rounded-full px-6 py-3 flex items-center gap-4 border border-divider">
|
||||
<span className="text-sm font-medium">
|
||||
已选 {selectedIds.size} 项
|
||||
</span>
|
||||
<div className="flex gap-2">
|
||||
<div className="fixed bottom-7 left-1/2 z-50 w-[calc(100vw-1rem)] max-w-max -translate-x-1/2 overflow-x-auto rounded-lg border border-divider bg-content1 p-2 shadow-lg">
|
||||
<div className="flex min-w-max items-center gap-2">
|
||||
<span className="text-sm font-medium shrink-0">
|
||||
已选 {selectedIds.size} 项
|
||||
</span>
|
||||
<Button size="sm" variant="flat" onPress={selectAll}>
|
||||
全选
|
||||
</Button>
|
||||
|
||||
@@ -854,31 +854,33 @@ export default function TunnelPage() {
|
||||
</div>
|
||||
|
||||
{selectMode && selectedIds.size > 0 && (
|
||||
<div className="fixed bottom-4 left-1/2 transform -translate-x-1/2 z-50 bg-content1 shadow-lg rounded-lg border border-divider p-3 flex items-center gap-3">
|
||||
<span className="text-sm text-default-600">已选择 {selectedIds.size} 项</span>
|
||||
<Button size="sm" variant="flat" onPress={selectAll}>
|
||||
全选
|
||||
</Button>
|
||||
<Button size="sm" variant="flat" onPress={deselectAll}>
|
||||
清空
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
color="danger"
|
||||
variant="flat"
|
||||
onPress={() => setBatchDeleteModalOpen(true)}
|
||||
>
|
||||
删除
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
color="primary"
|
||||
variant="flat"
|
||||
onPress={handleBatchRedeploy}
|
||||
isLoading={batchLoading}
|
||||
>
|
||||
下发
|
||||
</Button>
|
||||
<div className="fixed bottom-7 left-1/2 z-50 w-[calc(100vw-1rem)] max-w-max -translate-x-1/2 overflow-x-auto rounded-lg border border-divider bg-content1 p-2 shadow-lg">
|
||||
<div className="flex min-w-max items-center gap-2">
|
||||
<span className="text-sm text-default-600 shrink-0">已选择 {selectedIds.size} 项</span>
|
||||
<Button size="sm" variant="flat" onPress={selectAll}>
|
||||
全选
|
||||
</Button>
|
||||
<Button size="sm" variant="flat" onPress={deselectAll}>
|
||||
清空
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
color="danger"
|
||||
variant="flat"
|
||||
onPress={() => setBatchDeleteModalOpen(true)}
|
||||
>
|
||||
删除
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
color="primary"
|
||||
variant="flat"
|
||||
onPress={handleBatchRedeploy}
|
||||
isLoading={batchLoading}
|
||||
>
|
||||
下发
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user