mirror of
https://github.com/Sagit-chu/flvx.git
synced 2026-10-11 19:56:37 +08:00
Revert user page search bar to inline implementation for consistency
This commit is contained in:
+262
-146
@@ -1,4 +1,3 @@
|
|||||||
import { SearchBar } from "@/components/search-bar";
|
|
||||||
import { useState, useEffect, useMemo, useCallback, useRef } from "react";
|
import { useState, useEffect, useMemo, useCallback, useRef } from "react";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
import { parseDate } from "@internationalized/date";
|
import { parseDate } from "@internationalized/date";
|
||||||
@@ -60,12 +59,7 @@ import {
|
|||||||
getUserGroupList,
|
getUserGroupList,
|
||||||
getUserGroups,
|
getUserGroups,
|
||||||
} from "@/api";
|
} from "@/api";
|
||||||
import {
|
import { EditIcon, DeleteIcon, SettingsIcon, SearchIcon } from "@/components/icons";
|
||||||
SearchIcon,
|
|
||||||
EditIcon,
|
|
||||||
DeleteIcon,
|
|
||||||
SettingsIcon,
|
|
||||||
} from "@/components/icons";
|
|
||||||
import { PageLoadingState } from "@/components/page-state";
|
import { PageLoadingState } from "@/components/page-state";
|
||||||
import { useLocalStorageState } from "@/hooks/use-local-storage-state";
|
import { useLocalStorageState } from "@/hooks/use-local-storage-state";
|
||||||
import { removeItemsById, replaceItemById } from "@/utils/list-state";
|
import { removeItemsById, replaceItemById } from "@/utils/list-state";
|
||||||
@@ -784,6 +778,7 @@ export default function UserPage() {
|
|||||||
|
|
||||||
const handleQuotaReset = async (scope: "daily" | "monthly" | "all") => {
|
const handleQuotaReset = async (scope: "daily" | "monthly" | "all") => {
|
||||||
const userId = userForm.id;
|
const userId = userForm.id;
|
||||||
|
|
||||||
if (!userId) {
|
if (!userId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -888,34 +883,67 @@ export default function UserPage() {
|
|||||||
<AnimatedPage className="px-3 lg:px-6 py-8">
|
<AnimatedPage className="px-3 lg:px-6 py-8">
|
||||||
{/* 页面头部 */}
|
{/* 页面头部 */}
|
||||||
<div className="flex flex-col sm:flex-row items-stretch sm:items-center justify-between mb-6 gap-3">
|
<div className="flex flex-col sm:flex-row items-stretch sm:items-center justify-between mb-6 gap-3">
|
||||||
<div className={`flex-1 max-w-sm flex items-center gap-2 shrink-0 ${isSearchVisible ? "min-w-[200px]" : "min-w-0"}`}>
|
<div className="flex-1 max-w-sm flex items-center gap-2">
|
||||||
<SearchBar
|
{!isSearchVisible ? (
|
||||||
isVisible={isSearchVisible}
|
<Button
|
||||||
placeholder="搜索用户名"
|
isIconOnly
|
||||||
value={searchKeyword}
|
aria-label="搜索"
|
||||||
onChange={setSearchKeyword}
|
className="text-default-600"
|
||||||
onClose={() => {
|
color="default"
|
||||||
setIsSearchVisible(false);
|
size="sm"
|
||||||
setSearchKeyword("");
|
variant="flat"
|
||||||
}}
|
onPress={() => setIsSearchVisible(true)}
|
||||||
onOpen={() => {
|
>
|
||||||
setIsSearchVisible(true);
|
<SearchIcon className="w-4 h-4" />
|
||||||
setTimeout(() => {
|
</Button>
|
||||||
const searchInput = document.querySelector('input[placeholder*="搜索"]');
|
) : (
|
||||||
if (searchInput) (searchInput as HTMLElement).focus();
|
<div className="flex w-full items-center gap-2 animate-appearance-in">
|
||||||
}, 150);
|
<Input
|
||||||
}}
|
classNames={{
|
||||||
/>
|
base: "bg-default-100",
|
||||||
|
input: "bg-transparent",
|
||||||
|
inputWrapper:
|
||||||
|
"bg-default-100 border-2 border-default-200 hover:border-default-300 data-[hover=true]:border-default-300",
|
||||||
|
}}
|
||||||
|
placeholder="搜索用户名"
|
||||||
|
value={searchKeyword}
|
||||||
|
onChange={(e) => setSearchKeyword(e.target.value)}
|
||||||
|
onKeyDown={(e) => e.key === "Enter" && handleSearch()}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
isIconOnly
|
||||||
|
aria-label="关闭搜索"
|
||||||
|
className="text-default-600 shrink-0"
|
||||||
|
color="default"
|
||||||
|
size="sm"
|
||||||
|
variant="light"
|
||||||
|
onPress={() => {
|
||||||
|
setIsSearchVisible(false);
|
||||||
|
setSearchKeyword("");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className="w-4 h-4"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M6 18L18 6M6 6l12 12"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<Button
|
|
||||||
className="w-fit self-end sm:self-auto"
|
<Button color="primary" size="sm" variant="flat" onPress={handleAdd}>
|
||||||
color="primary"
|
新增
|
||||||
size="sm"
|
</Button>
|
||||||
variant="flat"
|
|
||||||
onPress={handleAdd}
|
|
||||||
>
|
|
||||||
新增
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 用户列表 */}
|
{/* 用户列表 */}
|
||||||
@@ -1126,8 +1154,11 @@ export default function UserPage() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 用户表单模态框 */}
|
{/* 用户表单模态框 */}
|
||||||
<Modal classNames={{ base: "!w-[calc(100%-32px)] !mx-auto sm:!w-full rounded-2xl" }}
|
<Modal
|
||||||
backdrop="blur"
|
backdrop="blur"
|
||||||
|
classNames={{
|
||||||
|
base: "!w-[calc(100%-32px)] !mx-auto sm:!w-full rounded-2xl",
|
||||||
|
}}
|
||||||
isOpen={isUserModalOpen}
|
isOpen={isUserModalOpen}
|
||||||
placement="center"
|
placement="center"
|
||||||
scrollBehavior="outside"
|
scrollBehavior="outside"
|
||||||
@@ -1281,8 +1312,8 @@ export default function UserPage() {
|
|||||||
<div className="rounded-lg bg-background p-3">
|
<div className="rounded-lg bg-background p-3">
|
||||||
<div className="text-xs text-default-500">每月用量</div>
|
<div className="text-xs text-default-500">每月用量</div>
|
||||||
<div className="mt-1 text-sm font-semibold text-foreground">
|
<div className="mt-1 text-sm font-semibold text-foreground">
|
||||||
{formatFlow(Number(editingUser.monthlyUsedBytes ?? 0))} /{" "}
|
{formatFlow(Number(editingUser.monthlyUsedBytes ?? 0))}{" "}
|
||||||
{formatQuotaLimit(editingUser.monthlyQuotaGB)}
|
/ {formatQuotaLimit(editingUser.monthlyQuotaGB)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1386,19 +1417,37 @@ export default function UserPage() {
|
|||||||
<h3 className="text-lg font-semibold mb-4">分配新权限</h3>
|
<h3 className="text-lg font-semibold mb-4">分配新权限</h3>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex flex-col gap-2 relative">
|
<div className="flex flex-col gap-2 relative">
|
||||||
<p className="text-base text-default-700 ml-1 font-medium">隧道列表</p>
|
<p className="text-base text-default-700 ml-1 font-medium">
|
||||||
|
隧道列表
|
||||||
|
</p>
|
||||||
|
|
||||||
{/* 顶部触发框 */}
|
{/* 顶部触发框 */}
|
||||||
<div
|
<div
|
||||||
className={`group flex items-center justify-between px-4 py-3 rounded-xl border-2 transition-all cursor-pointer shadow-sm w-[320px] ${isTunnelListExpanded ? "border-primary bg-white ring-2 ring-primary/10" : "border-default-200 bg-default-50 hover:border-primary-300"}`}
|
className={`group flex items-center justify-between px-4 py-3 rounded-xl border-2 transition-all cursor-pointer shadow-sm w-[320px] ${isTunnelListExpanded ? "border-primary bg-white ring-2 ring-primary/10" : "border-default-200 bg-default-50 hover:border-primary-300"}`}
|
||||||
onClick={() => setIsTunnelListExpanded(!isTunnelListExpanded)}
|
onClick={() =>
|
||||||
|
setIsTunnelListExpanded(!isTunnelListExpanded)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<span className={`text-sm truncate ${batchTunnelSelections.size > 0 ? "text-primary-500 font-bold" : "text-default-400"}`}>
|
<span
|
||||||
|
className={`text-sm truncate ${batchTunnelSelections.size > 0 ? "text-primary-500 font-bold" : "text-default-400"}`}
|
||||||
|
>
|
||||||
{batchTunnelSelections.size > 0
|
{batchTunnelSelections.size > 0
|
||||||
? `已选 ${batchTunnelSelections.size} 项:` + Array.from(batchTunnelSelections.keys()).map(id => tunnels.find(t => t.id === id)?.name).join('、')
|
? `已选 ${batchTunnelSelections.size} 项:` +
|
||||||
|
Array.from(batchTunnelSelections.keys())
|
||||||
|
.map(
|
||||||
|
(id) => tunnels.find((t) => t.id === id)?.name,
|
||||||
|
)
|
||||||
|
.join("、")
|
||||||
: "请选择隧道(勾选后配置限速)"}
|
: "请选择隧道(勾选后配置限速)"}
|
||||||
</span>
|
</span>
|
||||||
<svg className={`w-5 h-5 text-default-400 transition-transform ${isTunnelListExpanded ? "rotate-180 text-primary" : ""}`} fill="none" stroke="currentColor" viewBox="0 0 24 24"><path d="M19 9l-7 7-7-7" strokeWidth={2.5} /></svg>
|
<svg
|
||||||
|
className={`w-5 h-5 text-default-400 transition-transform ${isTunnelListExpanded ? "rotate-180 text-primary" : ""}`}
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path d="M19 9l-7 7-7-7" strokeWidth={2.5} />
|
||||||
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 列表悬浮层 */}
|
{/* 列表悬浮层 */}
|
||||||
@@ -1410,9 +1459,15 @@ export default function UserPage() {
|
|||||||
<div className="max-h-[350px] overflow-y-auto p-2 custom-scrollbar">
|
<div className="max-h-[350px] overflow-y-auto p-2 custom-scrollbar">
|
||||||
{tunnels.map((tunnel) => {
|
{tunnels.map((tunnel) => {
|
||||||
const isAssigned = isTunnelAssigned(tunnel.id);
|
const isAssigned = isTunnelAssigned(tunnel.id);
|
||||||
const isSelected = batchTunnelSelections.has(tunnel.id);
|
const isSelected = batchTunnelSelections.has(
|
||||||
const tunnelSpeedLimits = getSpeedLimitsForTunnel(tunnel.id);
|
tunnel.id,
|
||||||
const currentSpeedId = batchTunnelSelections.get(tunnel.id);
|
);
|
||||||
|
const tunnelSpeedLimits = getSpeedLimitsForTunnel(
|
||||||
|
tunnel.id,
|
||||||
|
);
|
||||||
|
const currentSpeedId = batchTunnelSelections.get(
|
||||||
|
tunnel.id,
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -1433,40 +1488,73 @@ export default function UserPage() {
|
|||||||
// 关键:禁用 Checkbox 自身的点击,防止它跟父容器打架
|
// 关键:禁用 Checkbox 自身的点击,防止它跟父容器打架
|
||||||
className="pointer-events-none"
|
className="pointer-events-none"
|
||||||
/>
|
/>
|
||||||
<span className={`text-sm font-medium truncate ${isSelected ? "text-primary-700" : ""}`}>{tunnel.name}</span>
|
<span
|
||||||
|
className={`text-sm font-medium truncate ${isSelected ? "text-primary-700" : ""}`}
|
||||||
|
>
|
||||||
|
{tunnel.name}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 右侧限速选择:复刻 image_24879b */}
|
{/* 右侧限速选择:复刻 image_24879b */}
|
||||||
{isSelected && !isAssigned && (
|
{isSelected && !isAssigned && (
|
||||||
<div className="flex items-center ml-2" onClick={(e) => e.stopPropagation()}>
|
<div
|
||||||
|
className="flex items-center ml-2"
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
<Select
|
<Select
|
||||||
aria-label="限速选择"
|
aria-label="限速选择"
|
||||||
className="w-32"
|
className="w-32"
|
||||||
|
placeholder="不限速"
|
||||||
|
selectedKeys={
|
||||||
|
currentSpeedId
|
||||||
|
? [currentSpeedId.toString()]
|
||||||
|
: []
|
||||||
|
}
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="flat"
|
variant="flat"
|
||||||
placeholder="不限速"
|
|
||||||
selectedKeys={currentSpeedId ? [currentSpeedId.toString()] : []}
|
|
||||||
onSelectionChange={(keys) => {
|
onSelectionChange={(keys) => {
|
||||||
const selectedKey = Array.from(keys)[0];
|
const selectedKey = Array.from(keys)[0];
|
||||||
updateTunnelSpeedLimit(tunnel.id, selectedKey ? Number(selectedKey) : null);
|
|
||||||
|
updateTunnelSpeedLimit(
|
||||||
|
tunnel.id,
|
||||||
|
selectedKey
|
||||||
|
? Number(selectedKey)
|
||||||
|
: null,
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{tunnelSpeedLimits.map(sl => <SelectItem key={sl.id.toString()}>{sl.name}</SelectItem>)}
|
{tunnelSpeedLimits.map((sl) => (
|
||||||
|
<SelectItem key={sl.id.toString()}>
|
||||||
|
{sl.name}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{isAssigned && <span className="text-[10px] text-default-400 italic pr-2">已分配</span>}
|
{isAssigned && (
|
||||||
|
<span className="text-[10px] text-default-400 italic pr-2">
|
||||||
|
已分配
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-default-50/80 border-t p-2 flex justify-end">
|
<div className="bg-default-50/80 border-t p-2 flex justify-end">
|
||||||
<Button size="md" variant="light" color="primary" className="font-bold" onPress={() => setIsTunnelListExpanded(false)}>完成配置</Button>
|
<Button
|
||||||
|
className="font-bold"
|
||||||
|
color="primary"
|
||||||
|
size="md"
|
||||||
|
variant="light"
|
||||||
|
onPress={() => setIsTunnelListExpanded(false)}
|
||||||
|
>
|
||||||
|
完成配置
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-wrap items-center gap-2">
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
<Button
|
<Button
|
||||||
className="w-fit px-8"
|
className="w-fit px-8"
|
||||||
color="primary"
|
color="primary"
|
||||||
@@ -1488,104 +1576,120 @@ export default function UserPage() {
|
|||||||
{/* 已有权限部分 */}
|
{/* 已有权限部分 */}
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-lg font-semibold mb-4">已有权限</h3>
|
<h3 className="text-lg font-semibold mb-4">已有权限</h3>
|
||||||
<Table
|
<Table
|
||||||
aria-label="用户隧道权限列表"
|
aria-label="用户隧道权限列表"
|
||||||
// 1. 去掉 layout="fixed",改为在 classNames.table 里写 table-fixed
|
// 1. 去掉 layout="fixed",改为在 classNames.table 里写 table-fixed
|
||||||
classNames={{
|
classNames={{
|
||||||
wrapper: "shadow-none p-0 min-w-[380px] overflow-x-auto",
|
wrapper: "shadow-none p-0 min-w-[380px] overflow-x-auto",
|
||||||
th: "bg-default-50 text-default-600 font-semibold",
|
th: "bg-default-50 text-default-600 font-semibold",
|
||||||
td: "py-4 border-b border-divider"
|
td: "py-4 border-b border-divider",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
<TableColumn className="w-[35%]">隧道名称</TableColumn>
|
<TableColumn className="w-[35%]">隧道名称</TableColumn>
|
||||||
<TableColumn className="w-[35%]">流量统计</TableColumn>
|
<TableColumn className="w-[35%]">流量统计</TableColumn>
|
||||||
<TableColumn className="w-[30%] text-right">操作</TableColumn>
|
<TableColumn className="w-[30%] text-right">
|
||||||
</TableHeader>
|
操作
|
||||||
<TableBody
|
</TableColumn>
|
||||||
emptyContent="暂无隧道权限"
|
</TableHeader>
|
||||||
isLoading={tunnelListLoading}
|
<TableBody
|
||||||
items={userTunnels}
|
emptyContent="暂无隧道权限"
|
||||||
loadingContent={<Spinner />}
|
isLoading={tunnelListLoading}
|
||||||
>
|
items={userTunnels}
|
||||||
{(userTunnel) => (
|
loadingContent={<Spinner />}
|
||||||
<TableRow key={userTunnel.id} className="hover:bg-default-50/50 transition-colors">
|
>
|
||||||
<TableCell>
|
{(userTunnel) => (
|
||||||
<div className="flex items-center gap-2">
|
<TableRow
|
||||||
<span className="font-bold text-default-700 whitespace-nowrap">
|
key={userTunnel.id}
|
||||||
{userTunnel.tunnelName}
|
className="hover:bg-default-50/50 transition-colors"
|
||||||
</span>
|
>
|
||||||
</div>
|
<TableCell>
|
||||||
</TableCell>
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="font-bold text-default-700 whitespace-nowrap">
|
||||||
|
{userTunnel.tunnelName}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</TableCell>
|
||||||
|
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<div className="flex items-center gap-1 whitespace-nowrap text-sm">
|
<div className="flex items-center gap-1 whitespace-nowrap text-sm">
|
||||||
<span className="text-danger font-mono font-bold">
|
<span className="text-danger font-mono font-bold">
|
||||||
{formatFlow(calculateTunnelUsedFlow(userTunnel))}
|
{formatFlow(calculateTunnelUsedFlow(userTunnel))}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-default-300">/</span>
|
<span className="text-default-300">/</span>
|
||||||
<span className="text-default-500 font-mono">
|
<span className="text-default-500 font-mono">
|
||||||
{formatFlow(userTunnel.flow, "gb")}
|
{formatFlow(userTunnel.flow, "gb")}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|
||||||
<TableCell>
|
<TableCell>
|
||||||
{/* 5. justify-end 确保按钮群组整体靠右 */}
|
{/* 5. justify-end 确保按钮群组整体靠右 */}
|
||||||
<div className="flex items-center justify-end gap-2">
|
<div className="flex items-center justify-end gap-2">
|
||||||
<Button
|
<Button
|
||||||
isIconOnly
|
isIconOnly
|
||||||
size="sm"
|
className="bg-blue-50 text-blue-600 hover:bg-blue-100 w-8 h-8 min-w-8"
|
||||||
variant="flat"
|
size="sm"
|
||||||
className="bg-blue-50 text-blue-600 hover:bg-blue-100 w-8 h-8 min-w-8"
|
variant="flat"
|
||||||
onPress={() => handleEditTunnel(userTunnel)}
|
onPress={() => handleEditTunnel(userTunnel)}
|
||||||
>
|
>
|
||||||
<EditIcon className="w-4 h-4" />
|
<EditIcon className="w-4 h-4" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
isIconOnly
|
isIconOnly
|
||||||
size="sm"
|
className="bg-orange-50 text-orange-600 hover:bg-orange-100 w-8 h-8 min-w-8"
|
||||||
variant="flat"
|
size="sm"
|
||||||
className="bg-orange-50 text-orange-600 hover:bg-orange-100 w-8 h-8 min-w-8"
|
variant="flat"
|
||||||
onPress={() => handleResetTunnelFlow(userTunnel)}
|
onPress={() => handleResetTunnelFlow(userTunnel)}
|
||||||
>
|
>
|
||||||
<svg className="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
|
<svg
|
||||||
<path clipRule="evenodd" d="M4 2a1 1 0 011 1v2.101a7.002 7.002 0 0111.601 2.566 1 1 0 11-1.885.666A5.002 5.002 0 005.999 7H9a1 1 0 010 2H4a1 1 0 01-1-1V3a1 1 0 011-1zm.008 9.057a1 1 0 011.276.61A5.002 5.002 0 0014.001 13H11a1 1 0 110-2h5a1 1 0 011 1v5a1 1 0 11-2 0v-2.101a7.002 7.002 0 01-11.601-2.566 1 1 0 01.61-1.276z" fillRule="evenodd" />
|
className="w-4 h-4"
|
||||||
</svg>
|
fill="currentColor"
|
||||||
</Button>
|
viewBox="0 0 20 20"
|
||||||
<Button
|
>
|
||||||
isIconOnly
|
<path
|
||||||
size="sm"
|
clipRule="evenodd"
|
||||||
variant="flat"
|
d="M4 2a1 1 0 011 1v2.101a7.002 7.002 0 0111.601 2.566 1 1 0 11-1.885.666A5.002 5.002 0 005.999 7H9a1 1 0 010 2H4a1 1 0 01-1-1V3a1 1 0 011-1zm.008 9.057a1 1 0 011.276.61A5.002 5.002 0 0014.001 13H11a1 1 0 110-2h5a1 1 0 011 1v5a1 1 0 11-2 0v-2.101a7.002 7.002 0 01-11.601-2.566 1 1 0 01.61-1.276z"
|
||||||
className="bg-danger-50 text-danger hover:bg-danger-100 w-8 h-8 min-w-8"
|
fillRule="evenodd"
|
||||||
onPress={() => handleRemoveTunnel(userTunnel)}
|
/>
|
||||||
>
|
</svg>
|
||||||
<DeleteIcon className="w-4 h-4" />
|
</Button>
|
||||||
</Button>
|
<Button
|
||||||
</div>
|
isIconOnly
|
||||||
</TableCell>
|
className="bg-danger-50 text-danger hover:bg-danger-100 w-8 h-8 min-w-8"
|
||||||
</TableRow>
|
size="sm"
|
||||||
)}
|
variant="flat"
|
||||||
</TableBody>
|
onPress={() => handleRemoveTunnel(userTunnel)}
|
||||||
</Table>
|
>
|
||||||
|
<DeleteIcon className="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
)}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
<ModalFooter className="justify-end">
|
<ModalFooter className="justify-end">
|
||||||
<Button
|
<Button
|
||||||
onPress={onTunnelModalClose}
|
color="primary"
|
||||||
color="primary"
|
variant="flat" // 建议加个 variant 保持和你其他按钮风格一致
|
||||||
variant="flat" // 建议加个 variant 保持和你其他按钮风格一致
|
onPress={onTunnelModalClose}
|
||||||
>
|
>
|
||||||
关闭
|
关闭
|
||||||
</Button>
|
</Button>
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
{/* 编辑隧道权限模态框 */}
|
{/* 编辑隧道权限模态框 */}
|
||||||
<Modal classNames={{ base: "!w-[calc(100%-32px)] !mx-auto sm:!w-full rounded-2xl" }}
|
<Modal
|
||||||
backdrop="blur"
|
backdrop="blur"
|
||||||
|
classNames={{
|
||||||
|
base: "!w-[calc(100%-32px)] !mx-auto sm:!w-full rounded-2xl",
|
||||||
|
}}
|
||||||
isDismissable={false}
|
isDismissable={false}
|
||||||
isOpen={isEditTunnelModalOpen}
|
isOpen={isEditTunnelModalOpen}
|
||||||
placement="center"
|
placement="center"
|
||||||
@@ -1762,8 +1866,11 @@ export default function UserPage() {
|
|||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
{/* 删除确认对话框 */}
|
{/* 删除确认对话框 */}
|
||||||
<Modal classNames={{ base: "!w-[calc(100%-32px)] !mx-auto sm:!w-full rounded-2xl" }}
|
<Modal
|
||||||
backdrop="blur"
|
backdrop="blur"
|
||||||
|
classNames={{
|
||||||
|
base: "!w-[calc(100%-32px)] !mx-auto sm:!w-full rounded-2xl",
|
||||||
|
}}
|
||||||
isOpen={isDeleteModalOpen}
|
isOpen={isDeleteModalOpen}
|
||||||
placement="center"
|
placement="center"
|
||||||
scrollBehavior="outside"
|
scrollBehavior="outside"
|
||||||
@@ -1805,8 +1912,11 @@ export default function UserPage() {
|
|||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
{/* 删除隧道权限确认对话框 */}
|
{/* 删除隧道权限确认对话框 */}
|
||||||
<Modal classNames={{ base: "!w-[calc(100%-32px)] !mx-auto sm:!w-full rounded-2xl" }}
|
<Modal
|
||||||
backdrop="blur"
|
backdrop="blur"
|
||||||
|
classNames={{
|
||||||
|
base: "!w-[calc(100%-32px)] !mx-auto sm:!w-full rounded-2xl",
|
||||||
|
}}
|
||||||
isOpen={isDeleteTunnelModalOpen}
|
isOpen={isDeleteTunnelModalOpen}
|
||||||
placement="center"
|
placement="center"
|
||||||
scrollBehavior="outside"
|
scrollBehavior="outside"
|
||||||
@@ -1850,8 +1960,11 @@ export default function UserPage() {
|
|||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
{/* 重置流量确认对话框 */}
|
{/* 重置流量确认对话框 */}
|
||||||
<Modal classNames={{ base: "!w-[calc(100%-32px)] !mx-auto sm:!w-full rounded-2xl" }}
|
<Modal
|
||||||
backdrop="blur"
|
backdrop="blur"
|
||||||
|
classNames={{
|
||||||
|
base: "!w-[calc(100%-32px)] !mx-auto sm:!w-full rounded-2xl",
|
||||||
|
}}
|
||||||
isOpen={isResetFlowModalOpen}
|
isOpen={isResetFlowModalOpen}
|
||||||
placement="center"
|
placement="center"
|
||||||
scrollBehavior="outside"
|
scrollBehavior="outside"
|
||||||
@@ -1939,8 +2052,11 @@ export default function UserPage() {
|
|||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
{/* 重置隧道流量确认对话框 */}
|
{/* 重置隧道流量确认对话框 */}
|
||||||
<Modal classNames={{ base: "!w-[calc(100%-32px)] !mx-auto sm:!w-full rounded-2xl" }}
|
<Modal
|
||||||
backdrop="blur"
|
backdrop="blur"
|
||||||
|
classNames={{
|
||||||
|
base: "!w-[calc(100%-32px)] !mx-auto sm:!w-full rounded-2xl",
|
||||||
|
}}
|
||||||
isOpen={isResetTunnelFlowModalOpen}
|
isOpen={isResetTunnelFlowModalOpen}
|
||||||
placement="center"
|
placement="center"
|
||||||
scrollBehavior="outside"
|
scrollBehavior="outside"
|
||||||
|
|||||||
Reference in New Issue
Block a user