fix: migrate frontend to Vite 7 and resolve lint warnings

This commit is contained in:
root
2026-02-06 06:09:17 +00:00
parent 35c8063ac5
commit efd5a107b9
23 changed files with 550 additions and 449 deletions
+1 -1
View File
@@ -19,7 +19,7 @@ jobs:
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: '20' node-version: '20.19.0'
- name: Install dependencies - name: Install dependencies
run: npm install --legacy-peer-deps run: npm install --legacy-peer-deps
+4 -4
View File
@@ -45,7 +45,7 @@
"@react-aria/visually-hidden": "3.8.25", "@react-aria/visually-hidden": "3.8.25",
"@react-types/shared": "3.30.0", "@react-types/shared": "3.30.0",
"@tailwindcss/postcss": "4.1.11", "@tailwindcss/postcss": "4.1.11",
"@tailwindcss/vite": "4.1.11", "@tailwindcss/vite": "^4.1.18",
"@types/react-beautiful-dnd": "^13.1.8", "@types/react-beautiful-dnd": "^13.1.8",
"axios": "^1.11.0", "axios": "^1.11.0",
"clsx": "2.1.1", "clsx": "2.1.1",
@@ -70,7 +70,7 @@
"@types/react-dom": "18.3.0", "@types/react-dom": "18.3.0",
"@typescript-eslint/eslint-plugin": "8.31.1", "@typescript-eslint/eslint-plugin": "8.31.1",
"@typescript-eslint/parser": "8.31.1", "@typescript-eslint/parser": "8.31.1",
"@vitejs/plugin-react": "4.4.1", "@vitejs/plugin-react": "^5.1.3",
"eslint": "9.39.2", "eslint": "9.39.2",
"eslint-config-prettier": "9.1.0", "eslint-config-prettier": "9.1.0",
"eslint-plugin-import": "2.31.0", "eslint-plugin-import": "2.31.0",
@@ -84,7 +84,7 @@
"postcss": "8.5.6", "postcss": "8.5.6",
"prettier": "3.5.3", "prettier": "3.5.3",
"typescript": "5.6.3", "typescript": "5.6.3",
"vite": "5.4.21", "vite": "^7.3.1",
"vite-tsconfig-paths": "4.3.2" "vite-tsconfig-paths": "^6.0.5"
} }
} }
+1 -3
View File
@@ -148,9 +148,7 @@ function App() {
if (cachedAppName && cachedAppName !== document.title) { if (cachedAppName && cachedAppName !== document.title) {
document.title = cachedAppName; document.title = cachedAppName;
} }
} catch (error) { } catch {}
console.warn("检查标题更新失败:", error);
}
}; };
// 延迟检查,避免阻塞初始渲染 // 延迟检查,避免阻塞初始渲染
-4
View File
@@ -98,8 +98,6 @@ const Network = {
resolve(response.data); resolve(response.data);
}) })
.catch(function (error: any) { .catch(function (error: any) {
console.error("GET请求错误:", error);
// 检查是否是401错误(token失效) // 检查是否是401错误(token失效)
if (error.response && error.response.status === 401) { if (error.response && error.response.status === 401) {
handleTokenExpired(); handleTokenExpired();
@@ -146,8 +144,6 @@ const Network = {
resolve(response.data); resolve(response.data);
}) })
.catch(function (error: any) { .catch(function (error: any) {
console.error("POST请求错误:", error);
// 检查是否是401错误(token失效) // 检查是否是401错误(token失效)
if (error.response && error.response.status === 401) { if (error.response && error.response.status === 401) {
handleTokenExpired(); handleTokenExpired();
+2 -6
View File
@@ -33,9 +33,7 @@ export const Navbar = () => {
// 同步更新siteConfig // 同步更新siteConfig
siteConfig.name = cachedAppName; siteConfig.name = cachedAppName;
} }
} catch (error) { } catch {}
console.warn("检查配置更新失败:", error);
}
}; };
// 延迟执行,避免阻塞初始渲染 // 延迟执行,避免阻塞初始渲染
@@ -50,9 +48,7 @@ export const Navbar = () => {
setAppName(cachedAppName); setAppName(cachedAppName);
siteConfig.name = cachedAppName; siteConfig.name = cachedAppName;
} }
} catch (error) { } catch {}
console.warn("更新配置失败:", error);
}
}; };
window.addEventListener("configUpdated", handleConfigUpdate); window.addEventListener("configUpdated", handleConfigUpdate);
+1 -1
View File
@@ -126,7 +126,7 @@ export const getCachedConfigs = async (): Promise<Record<string, string>> => {
return configs; return configs;
} }
} catch (error) { } catch {
// API失败时返回缓存的数据 // API失败时返回缓存的数据
if (hasCachedData) { if (hasCachedData) {
return cachedConfigs; return cachedConfigs;
+4 -3
View File
@@ -260,9 +260,8 @@ export default function AdminLayout({
} else { } else {
toast.error(response.msg || "密码修改失败"); toast.error(response.msg || "密码修改失败");
} }
} catch (error) { } catch {
toast.error("修改密码时发生错误"); toast.error("修改密码时发生错误");
console.error("修改密码错误:", error);
} finally { } finally {
setPasswordLoading(false); setPasswordLoading(false);
} }
@@ -289,8 +288,10 @@ export default function AdminLayout({
> >
{/* 移动端遮罩层 */} {/* 移动端遮罩层 */}
{isMobile && mobileMenuVisible && ( {isMobile && mobileMenuVisible && (
<div <button
aria-label="关闭菜单"
className="fixed inset-0 backdrop-blur-sm bg-white/50 dark:bg-black/30 z-40" className="fixed inset-0 backdrop-blur-sm bg-white/50 dark:bg-black/30 z-40"
type="button"
onClick={hideMobileMenu} onClick={hideMobileMenu}
/> />
)} )}
+1 -1
View File
@@ -17,7 +17,7 @@ export default function H5SimpleLayout({
React.useEffect(() => { React.useEffect(() => {
try { try {
window.scrollTo({ top: 0, left: 0, behavior: "auto" }); window.scrollTo({ top: 0, left: 0, behavior: "auto" });
} catch (e) { } catch {
window.scrollTo(0, 0); window.scrollTo(0, 0);
} }
document.body.scrollTop = 0; document.body.scrollTop = 0;
+1 -1
View File
@@ -108,7 +108,7 @@ export default function H5Layout({ children }: { children: React.ReactNode }) {
useEffect(() => { useEffect(() => {
try { try {
window.scrollTo({ top: 0, left: 0, behavior: "auto" }); window.scrollTo({ top: 0, left: 0, behavior: "auto" });
} catch (e) { } catch {
window.scrollTo(0, 0); window.scrollTo(0, 0);
} }
document.body.scrollTop = 0; document.body.scrollTop = 0;
+1 -2
View File
@@ -89,8 +89,7 @@ export default function ChangePasswordPage() {
} else { } else {
toast.error(response.msg || "账号密码修改失败"); toast.error(response.msg || "账号密码修改失败");
} }
} catch (error) { } catch {
console.error("修改账号密码错误:", error);
toast.error("修改账号密码时发生错误"); toast.error("修改账号密码时发生错误");
} finally { } finally {
setLoading(false); setLoading(false);
+3 -3
View File
@@ -121,7 +121,7 @@ const getInitialConfigs = (): Record<string, string> => {
initialConfigs[key] = cachedValue; initialConfigs[key] = cachedValue;
} }
}); });
} catch (error) {} } catch {}
return initialConfigs; return initialConfigs;
}; };
@@ -172,7 +172,7 @@ export default function ConfigPage() {
setHasChanges(false); setHasChanges(false);
} else { } else {
} }
} catch (error) { } catch {
// 只有在没有缓存数据时才显示错误 // 只有在没有缓存数据时才显示错误
if (!hasInitialData) { if (!hasInitialData) {
toast.error("加载配置出错,请重试"); toast.error("加载配置出错,请重试");
@@ -250,7 +250,7 @@ export default function ConfigPage() {
} else { } else {
toast.error("保存配置失败: " + response.msg); toast.error("保存配置失败: " + response.msg);
} }
} catch (error) { } catch {
toast.error("保存配置出错,请重试"); toast.error("保存配置出错,请重试");
} finally { } finally {
setSaving(false); setSaving(false);
+16 -13
View File
@@ -207,8 +207,7 @@ export default function DashboardPage() {
} else { } else {
toast.error(res.msg || "获取套餐信息失败"); toast.error(res.msg || "获取套餐信息失败");
} }
} catch (error) { } catch {
console.error("获取套餐信息失败:", error);
toast.error("获取套餐信息失败"); toast.error("获取套餐信息失败");
} finally { } finally {
setLoading(false); setLoading(false);
@@ -648,7 +647,7 @@ export default function DashboardPage() {
try { try {
await navigator.clipboard.writeText(text); await navigator.clipboard.writeText(text);
toast.success(`已复制`); toast.success(`已复制`);
} catch (error) { } catch {
toast.error("复制失败"); toast.error("复制失败");
} }
}; };
@@ -661,7 +660,7 @@ export default function DashboardPage() {
), ),
); );
await copyToClipboard(addressItem.address); await copyToClipboard(addressItem.address);
} catch (error) { } catch {
toast.error("复制失败"); toast.error("复制失败");
} finally { } finally {
setAddressList((prev) => setAddressList((prev) =>
@@ -1189,14 +1188,15 @@ export default function DashboardPage() {
{forward.name} {forward.name}
</h4> </h4>
<div className="space-y-1"> <div className="space-y-1">
<code <button
className={`block px-2 py-1 bg-green-100 dark:bg-green-500/20 text-green-700 dark:text-green-300 rounded font-mono text-xs truncate ${hasMultipleIps(forward.inIp) ? "cursor-pointer hover:bg-green-200 dark:hover:bg-green-500/30" : ""}`} className={`block px-2 py-1 bg-green-100 dark:bg-green-500/20 text-green-700 dark:text-green-300 rounded font-mono text-xs truncate ${hasMultipleIps(forward.inIp) ? "cursor-pointer hover:bg-green-200 dark:hover:bg-green-500/30" : ""}`}
disabled={!hasMultipleIps(forward.inIp)}
title={formatInAddress( title={formatInAddress(
forward.inIp, forward.inIp,
forward.inPort, forward.inPort,
)} )}
type="button"
onClick={() => onClick={() =>
hasMultipleIps(forward.inIp) &&
showAddressModal( showAddressModal(
forward.inIp, forward.inIp,
forward.inPort, forward.inPort,
@@ -1205,17 +1205,20 @@ export default function DashboardPage() {
} }
> >
{formatInAddress(forward.inIp, forward.inPort)} {formatInAddress(forward.inIp, forward.inPort)}
</code> </button>
<div className="text-center text-default-400 text-xs"> <div className="text-center text-default-400 text-xs">
↓ ↓
</div> </div>
<code <button
className={`block px-2 py-1 bg-blue-100 dark:bg-blue-500/20 text-blue-700 dark:text-blue-300 rounded font-mono text-xs truncate ${hasMultipleRemoteAddresses(forward.remoteAddr) ? "cursor-pointer hover:bg-blue-200 dark:hover:bg-blue-500/30" : ""}`} className={`block px-2 py-1 bg-blue-100 dark:bg-blue-500/20 text-blue-700 dark:text-blue-300 rounded font-mono text-xs truncate ${hasMultipleRemoteAddresses(forward.remoteAddr) ? "cursor-pointer hover:bg-blue-200 dark:hover:bg-blue-500/30" : ""}`}
title={formatRemoteAddress(forward.remoteAddr)} disabled={
onClick={() => !hasMultipleRemoteAddresses(
hasMultipleRemoteAddresses(
forward.remoteAddr, forward.remoteAddr,
) && )
}
title={formatRemoteAddress(forward.remoteAddr)}
type="button"
onClick={() =>
showRemoteAddressModal( showRemoteAddressModal(
forward.remoteAddr, forward.remoteAddr,
"出口地址", "出口地址",
@@ -1223,7 +1226,7 @@ export default function DashboardPage() {
} }
> >
{formatRemoteAddress(forward.remoteAddr)} {formatRemoteAddress(forward.remoteAddr)}
</code> </button>
</div> </div>
</div> </div>
File diff suppressed because it is too large Load Diff
+2 -5
View File
@@ -156,8 +156,7 @@ export default function IndexPage() {
tacInstanceRef.current = new window.TAC(config, style); tacInstanceRef.current = new window.TAC(config, style);
tacInstanceRef.current.init(); tacInstanceRef.current.init();
} catch (error) { } catch {
console.error("初始化验证码失败:", error);
toast.error("验证码初始化失败,请刷新页面重试"); toast.error("验证码初始化失败,请刷新页面重试");
setShowCaptcha(false); setShowCaptcha(false);
setLoading(false); setLoading(false);
@@ -202,8 +201,7 @@ export default function IndexPage() {
// 登录成功 // 登录成功
toast.success("登录成功"); toast.success("登录成功");
navigate("/dashboard"); navigate("/dashboard");
} catch (error) { } catch {
console.error("登录错误:", error);
toast.error("网络错误,请稍后重试"); toast.error("网络错误,请稍后重试");
} finally { } finally {
setLoading(false); setLoading(false);
@@ -239,7 +237,6 @@ export default function IndexPage() {
}, 100); }, 100);
} }
} catch (error) { } catch (error) {
console.error("检查验证码状态错误:", error);
toast.error("网络错误,请稍后重试" + error); toast.error("网络错误,请稍后重试" + error);
setLoading(false); setLoading(false);
} }
+74 -62
View File
@@ -94,10 +94,8 @@ export default function LimitPage() {
if (tunnelsRes.code === 0) { if (tunnelsRes.code === 0) {
setTunnels(tunnelsRes.data || []); setTunnels(tunnelsRes.data || []);
} else { } else {
console.warn("获取隧道列表失败:", tunnelsRes.msg);
} }
} catch (error) { } catch {
console.error("加载数据失败:", error);
toast.error("加载数据失败"); toast.error("加载数据失败");
} finally { } finally {
setLoading(false); setLoading(false);
@@ -177,8 +175,7 @@ export default function LimitPage() {
} else { } else {
toast.error(res.msg || "删除失败"); toast.error(res.msg || "删除失败");
} }
} catch (error) { } catch {
console.error("删除失败:", error);
toast.error("删除失败"); toast.error("删除失败");
} finally { } finally {
setDeleteLoading(false); setDeleteLoading(false);
@@ -196,7 +193,9 @@ export default function LimitPage() {
if (isEdit) { if (isEdit) {
res = await updateSpeedLimit(form); res = await updateSpeedLimit(form);
} else { } else {
const { id, ...createData } = form; const createData = { ...form };
delete createData.id;
res = await createSpeedLimit(createData); res = await createSpeedLimit(createData);
} }
@@ -208,8 +207,7 @@ export default function LimitPage() {
} else { } else {
toast.error(res.msg || "操作失败"); toast.error(res.msg || "操作失败");
} }
} catch (error) { } catch {
console.error("提交失败:", error);
toast.error("操作失败"); toast.error("操作失败");
} finally { } finally {
setSubmitLoading(false); setSubmitLoading(false);
@@ -233,7 +231,7 @@ export default function LimitPage() {
<div className="flex items-center justify-between mb-6"> <div className="flex items-center justify-between mb-6">
<div className="flex-1" /> <div className="flex-1" />
<Button size="sm" variant="flat" color="primary" onPress={handleAdd}> <Button color="primary" size="sm" variant="flat" onPress={handleAdd}>
新增 新增
</Button> </Button>
</div> </div>
@@ -255,8 +253,8 @@ export default function LimitPage() {
</div> </div>
<Chip <Chip
color={rule.status === 1 ? "success" : "danger"} color={rule.status === 1 ? "success" : "danger"}
variant="flat"
size="sm" size="sm"
variant="flat"
> >
{rule.status === 1 ? "运行" : "异常"} {rule.status === 1 ? "运行" : "异常"}
</Chip> </Chip>
@@ -291,24 +289,26 @@ export default function LimitPage() {
<div className="flex gap-2 mt-4"> <div className="flex gap-2 mt-4">
<Button <Button
className="flex-1" className="flex-1"
color="primary" color="primary"
size="sm" size="sm"
startContent={ startContent={
<svg className="w-4 h-4" fill="currentColor" viewBox="0 0 20 20"> <svg
<path d="M13.586 3.586a2 2 0 112.828 2.828l-.793.793-2.828-2.828.793-.793zM11.379 5.793L3 14.172V17h2.828l8.38-8.379-2.83-2.828z" /> className="w-4 h-4"
</svg> fill="currentColor"
} viewBox="0 0 20 20"
variant="flat" >
onPress={() => handleEdit(rule)} <path d="M13.586 3.586a2 2 0 112.828 2.828l-.793.793-2.828-2.828.793-.793zM11.379 5.793L3 14.172V17h2.828l8.38-8.379-2.83-2.828z" />
</svg>
}
variant="flat"
onPress={() => handleEdit(rule)}
> >
编辑 编辑
</Button> </Button>
<Button <Button
size="sm"
variant="flat"
color="danger"
onPress={() => handleDelete(rule)}
className="flex-1" className="flex-1"
color="danger"
size="sm"
startContent={ startContent={
<svg <svg
className="w-4 h-4" className="w-4 h-4"
@@ -316,17 +316,19 @@ export default function LimitPage() {
viewBox="0 0 20 20" viewBox="0 0 20 20"
> >
<path <path
fillRule="evenodd"
d="M9 2a1 1 0 000 2h2a1 1 0 100-2H9z"
clipRule="evenodd" clipRule="evenodd"
d="M9 2a1 1 0 000 2h2a1 1 0 100-2H9z"
fillRule="evenodd"
/> />
<path <path
fillRule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zM8 7a1 1 0 012 0v4a1 1 0 11-2 0V7zM12 7a1 1 0 012 0v4a1 1 0 11-2 0V7z"
clipRule="evenodd" clipRule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zM8 7a1 1 0 012 0v4a1 1 0 11-2 0V7zM12 7a1 1 0 012 0v4a1 1 0 11-2 0V7z"
fillRule="evenodd"
/> />
</svg> </svg>
} }
variant="flat"
onPress={() => handleDelete(rule)}
> >
删除 删除
</Button> </Button>
@@ -348,10 +350,10 @@ export default function LimitPage() {
viewBox="0 0 24 24" viewBox="0 0 24 24"
> >
<path <path
d="M12 6v6l4 2m6-6a9 9 0 11-18 0 9 9 0 0118 0z"
strokeLinecap="round" strokeLinecap="round"
strokeLinejoin="round" strokeLinejoin="round"
strokeWidth={1.5} strokeWidth={1.5}
d="M12 6v6l4 2m6-6a9 9 0 11-18 0 9 9 0 0118 0z"
/> />
</svg> </svg>
</div> </div>
@@ -370,12 +372,12 @@ export default function LimitPage() {
{/* 新增/编辑模态框 */} {/* 新增/编辑模态框 */}
<Modal <Modal
isOpen={modalOpen}
onOpenChange={setModalOpen}
size="2xl"
scrollBehavior="outside"
backdrop="blur" backdrop="blur"
isOpen={modalOpen}
placement="center" placement="center"
scrollBehavior="outside"
size="2xl"
onOpenChange={setModalOpen}
> >
<ModalContent> <ModalContent>
{(onClose) => ( {(onClose) => (
@@ -394,36 +396,50 @@ export default function LimitPage() {
<div className="space-y-4"> <div className="space-y-4">
<Input <Input
errorMessage={errors.name} errorMessage={errors.name}
isInvalid={!!errors.name} isInvalid={!!errors.name}
label="规则名称" label="规则名称"
placeholder="请输入限速规则名称" placeholder="请输入限速规则名称"
value={form.name} value={form.name}
variant="bordered" variant="bordered"
onChange={(e) => setForm(prev => ({ ...prev, name: e.target.value }))} onChange={(e) =>
setForm((prev) => ({ ...prev, name: e.target.value }))
}
/> />
<Input <Input
endContent={ endContent={
<div className="pointer-events-none flex items-center"> <div className="pointer-events-none flex items-center">
<span className="text-default-400 text-small">Mbps</span> <span className="text-default-400 text-small">
</div> Mbps
} </span>
errorMessage={errors.speed} </div>
isInvalid={!!errors.speed} }
label="速度限制" errorMessage={errors.speed}
placeholder="请输入速度限制" isInvalid={!!errors.speed}
type="number" label="速度限制"
value={form.speed.toString()} placeholder="请输入速度限制"
variant="bordered" type="number"
onChange={(e) => setForm(prev => ({ ...prev, speed: parseInt(e.target.value) || 0 }))} value={form.speed.toString()}
variant="bordered"
onChange={(e) =>
setForm((prev) => ({
...prev,
speed: parseInt(e.target.value) || 0,
}))
}
/> />
<Select <Select
description={isEdit ? "编辑时无法修改绑定隧道" : undefined}
errorMessage={errors.tunnelId}
isDisabled={isEdit}
isInvalid={!!errors.tunnelId}
label="绑定隧道" label="绑定隧道"
placeholder="请选择要绑定的隧道" placeholder="请选择要绑定的隧道"
selectedKeys={ selectedKeys={
form.tunnelId ? [form.tunnelId.toString()] : [] form.tunnelId ? [form.tunnelId.toString()] : []
} }
variant="bordered"
onSelectionChange={(keys) => { onSelectionChange={(keys) => {
const selectedKey = Array.from(keys)[0] as string; const selectedKey = Array.from(keys)[0] as string;
@@ -431,6 +447,7 @@ export default function LimitPage() {
const selectedTunnel = tunnels.find( const selectedTunnel = tunnels.find(
(tunnel) => tunnel.id === parseInt(selectedKey), (tunnel) => tunnel.id === parseInt(selectedKey),
); );
setForm((prev) => ({ setForm((prev) => ({
...prev, ...prev,
tunnelId: parseInt(selectedKey), tunnelId: parseInt(selectedKey),
@@ -444,11 +461,6 @@ export default function LimitPage() {
})); }));
} }
}} }}
isInvalid={!!errors.tunnelId}
errorMessage={errors.tunnelId}
variant="bordered"
isDisabled={isEdit}
description={isEdit ? "编辑时无法修改绑定隧道" : undefined}
> >
{tunnels.map((tunnel) => ( {tunnels.map((tunnel) => (
<SelectItem key={tunnel.id}>{tunnel.name}</SelectItem> <SelectItem key={tunnel.id}>{tunnel.name}</SelectItem>
@@ -462,8 +474,8 @@ export default function LimitPage() {
</Button> </Button>
<Button <Button
color="primary" color="primary"
onPress={handleSubmit}
isLoading={submitLoading} isLoading={submitLoading}
onPress={handleSubmit}
> >
{isEdit ? "保存修改" : "创建规则"} {isEdit ? "保存修改" : "创建规则"}
</Button> </Button>
@@ -475,12 +487,12 @@ export default function LimitPage() {
{/* 删除确认模态框 */} {/* 删除确认模态框 */}
<Modal <Modal
isOpen={deleteModalOpen}
onOpenChange={setDeleteModalOpen}
size="2xl"
scrollBehavior="outside"
backdrop="blur" backdrop="blur"
isOpen={deleteModalOpen}
placement="center" placement="center"
scrollBehavior="outside"
size="2xl"
onOpenChange={setDeleteModalOpen}
> >
<ModalContent> <ModalContent>
{(onClose) => ( {(onClose) => (
@@ -492,7 +504,7 @@ export default function LimitPage() {
<p className="text-default-600"> <p className="text-default-600">
确定要删除限速规则{" "} 确定要删除限速规则{" "}
<span className="font-semibold text-foreground"> <span className="font-semibold text-foreground">
"{ruleToDelete?.name}" &quot;{ruleToDelete?.name}&quot;
</span>{" "} </span>{" "}
吗? 吗?
</p> </p>
@@ -506,8 +518,8 @@ export default function LimitPage() {
</Button> </Button>
<Button <Button
color="danger" color="danger"
onPress={confirmDelete}
isLoading={deleteLoading} isLoading={deleteLoading}
onPress={confirmDelete}
> >
确认删除 确认删除
</Button> </Button>
+22 -19
View File
@@ -266,7 +266,7 @@ export default function NodePage() {
} else { } else {
toast.error(res.msg || "加载节点列表失败"); toast.error(res.msg || "加载节点列表失败");
} }
} catch (error) { } catch {
toast.error("网络错误,请重试"); toast.error("网络错误,请重试");
} finally { } finally {
setLoading(false); setLoading(false);
@@ -312,7 +312,7 @@ export default function NodePage() {
const data = JSON.parse(event.data); const data = JSON.parse(event.data);
handleWebSocketMessage(data); handleWebSocketMessage(data);
} catch (error) { } catch {
// 解析失败时不输出错误信息 // 解析失败时不输出错误信息
} }
}; };
@@ -327,7 +327,7 @@ export default function NodePage() {
setWsConnecting(false); setWsConnecting(false);
attemptReconnect(); attemptReconnect();
}; };
} catch (error) { } catch {
setWsConnected(false); setWsConnected(false);
setWsConnecting(false); setWsConnecting(false);
attemptReconnect(); attemptReconnect();
@@ -413,7 +413,7 @@ export default function NodePage() {
uptime: currentUptime, uptime: currentUptime,
}, },
}; };
} catch (error) { } catch {
return node; return node;
} }
} }
@@ -717,7 +717,7 @@ export default function NodePage() {
} else { } else {
toast.error(res.msg || "删除失败"); toast.error(res.msg || "删除失败");
} }
} catch (error) { } catch {
toast.error("网络错误,请重试"); toast.error("网络错误,请重试");
} finally { } finally {
setDeleteLoading(false); setDeleteLoading(false);
@@ -737,7 +737,7 @@ export default function NodePage() {
try { try {
await navigator.clipboard.writeText(res.data); await navigator.clipboard.writeText(res.data);
toast.success("安装命令已复制到剪贴板"); toast.success("安装命令已复制到剪贴板");
} catch (copyError) { } catch {
// 复制失败,显示安装命令模态框 // 复制失败,显示安装命令模态框
setInstallCommand(res.data); setInstallCommand(res.data);
setCurrentNodeName(node.name); setCurrentNodeName(node.name);
@@ -746,7 +746,7 @@ export default function NodePage() {
} else { } else {
toast.error(res.msg || "获取安装命令失败"); toast.error(res.msg || "获取安装命令失败");
} }
} catch (error) { } catch {
toast.error("获取安装命令失败"); toast.error("获取安装命令失败");
} finally { } finally {
setNodeList((prev) => setNodeList((prev) =>
@@ -761,7 +761,7 @@ export default function NodePage() {
await navigator.clipboard.writeText(installCommand); await navigator.clipboard.writeText(installCommand);
toast.success("安装命令已复制到剪贴板"); toast.success("安装命令已复制到剪贴板");
setInstallCommandModal(false); setInstallCommandModal(false);
} catch (error) { } catch {
toast.error("复制失败,请手动选择文本复制"); toast.error("复制失败,请手动选择文本复制");
} }
}; };
@@ -774,13 +774,13 @@ export default function NodePage() {
try { try {
const apiCall = isEdit ? updateNode : createNode; const apiCall = isEdit ? updateNode : createNode;
const { serverHost: _serverHost, ...rest } = form; const { serverHost, ...rest } = form;
const data = { const data = {
...rest, ...rest,
serverIp: serverIp:
form.serverIpV4?.trim() || form.serverIpV4?.trim() ||
form.serverIpV6?.trim() || form.serverIpV6?.trim() ||
form.serverHost?.trim() || serverHost?.trim() ||
"", "",
}; };
@@ -821,7 +821,7 @@ export default function NodePage() {
} else { } else {
toast.error(res.msg || (isEdit ? "更新失败" : "创建失败")); toast.error(res.msg || (isEdit ? "更新失败" : "创建失败"));
} }
} catch (error) { } catch {
toast.error("网络错误,请重试"); toast.error("网络错误,请重试");
} finally { } finally {
setSubmitLoading(false); setSubmitLoading(false);
@@ -871,9 +871,7 @@ export default function NodePage() {
// 保存到 localStorage // 保存到 localStorage
try { try {
localStorage.setItem("node-order", JSON.stringify(newOrder)); localStorage.setItem("node-order", JSON.stringify(newOrder));
} catch (error) { } catch {}
console.warn("无法保存节点排序到localStorage:", error);
}
// 持久化到数据库 // 持久化到数据库
try { try {
@@ -891,8 +889,7 @@ export default function NodePage() {
} else { } else {
toast.error("保存排序失败:" + (response.msg || "未知错误")); toast.error("保存排序失败:" + (response.msg || "未知错误"));
} }
} catch (error) { } catch {
console.error("保存节点排序到数据库失败:", error);
toast.error("保存排序失败,请重试"); toast.error("保存排序失败,请重试");
} }
}; };
@@ -903,6 +900,7 @@ export default function NodePage() {
if (prev) { if (prev) {
setSelectedIds(new Set()); setSelectedIds(new Set());
} }
return !prev; return !prev;
}); });
}; };
@@ -910,11 +908,13 @@ export default function NodePage() {
const toggleSelect = (id: number) => { const toggleSelect = (id: number) => {
setSelectedIds((prev) => { setSelectedIds((prev) => {
const next = new Set(prev); const next = new Set(prev);
if (next.has(id)) { if (next.has(id)) {
next.delete(id); next.delete(id);
} else { } else {
next.add(id); next.add(id);
} }
return next; return next;
}); });
}; };
@@ -932,6 +932,7 @@ export default function NodePage() {
setBatchLoading(true); setBatchLoading(true);
try { try {
const res = await batchDeleteNodes(Array.from(selectedIds)); const res = await batchDeleteNodes(Array.from(selectedIds));
if (res.code === 0) { if (res.code === 0) {
toast.success(`成功删除 ${selectedIds.size} 个节点`); toast.success(`成功删除 ${selectedIds.size} 个节点`);
setNodeList((prev) => prev.filter((n) => !selectedIds.has(n.id))); setNodeList((prev) => prev.filter((n) => !selectedIds.has(n.id)));
@@ -941,7 +942,7 @@ export default function NodePage() {
} else { } else {
toast.error(res.msg || "删除失败"); toast.error(res.msg || "删除失败");
} }
} catch (error) { } catch {
toast.error("网络错误,请重试"); toast.error("网络错误,请重试");
} finally { } finally {
setBatchLoading(false); setBatchLoading(false);
@@ -1724,7 +1725,8 @@ export default function NodePage() {
</ModalHeader> </ModalHeader>
<ModalBody> <ModalBody>
<p> <p>
确定要删除节点 <strong>"{nodeToDelete?.name}"</strong> 吗? 确定要删除节点{" "}
<strong>&quot;{nodeToDelete?.name}&quot;</strong> 吗?
</p> </p>
<p className="text-small text-default-500"> <p className="text-small text-default-500">
此操作不可恢复,请谨慎操作。 此操作不可恢复,请谨慎操作。
@@ -1818,7 +1820,8 @@ export default function NodePage() {
</ModalHeader> </ModalHeader>
<ModalBody> <ModalBody>
<p> <p>
确定要删除选中的 <strong>{selectedIds.size}</strong> 个节点吗? 确定要删除选中的 <strong>{selectedIds.size}</strong>{" "}
个节点吗?
</p> </p>
<p className="text-small text-default-500"> <p className="text-small text-default-500">
此操作不可恢复,请谨慎操作。 此操作不可恢复,请谨慎操作。
+1 -2
View File
@@ -168,9 +168,8 @@ export default function ProfilePage() {
} else { } else {
toast.error(response.msg || "密码修改失败"); toast.error(response.msg || "密码修改失败");
} }
} catch (error) { } catch {
toast.error("修改密码时发生错误"); toast.error("修改密码时发生错误");
console.error("修改密码错误:", error);
} finally { } finally {
setPasswordLoading(false); setPasswordLoading(false);
} }
+38 -27
View File
@@ -220,10 +220,8 @@ export default function TunnelPage() {
if (nodesRes.code === 0) { if (nodesRes.code === 0) {
setNodes(nodesRes.data || []); setNodes(nodesRes.data || []);
} else { } else {
console.warn("获取节点列表失败:", nodesRes.msg);
} }
} catch (error) { } catch {
console.error("加载数据失败:", error);
toast.error("加载数据失败"); toast.error("加载数据失败");
} finally { } finally {
setLoading(false); setLoading(false);
@@ -356,8 +354,7 @@ export default function TunnelPage() {
} else { } else {
toast.error(response.msg || "删除失败"); toast.error(response.msg || "删除失败");
} }
} catch (error) { } catch {
console.error("删除失败:", error);
toast.error("删除失败"); toast.error("删除失败");
} finally { } finally {
setDeleteLoading(false); setDeleteLoading(false);
@@ -498,8 +495,7 @@ export default function TunnelPage() {
} else { } else {
toast.error(response.msg || (isEdit ? "更新失败" : "创建失败")); toast.error(response.msg || (isEdit ? "更新失败" : "创建失败"));
} }
} catch (error) { } catch {
console.error("提交失败:", error);
toast.error("网络错误,请重试"); toast.error("网络错误,请重试");
} finally { } finally {
setSubmitLoading(false); setSubmitLoading(false);
@@ -537,8 +533,7 @@ export default function TunnelPage() {
], ],
}); });
} }
} catch (error) { } catch {
console.error("诊断失败:", error);
toast.error("网络错误,请重试"); toast.error("网络错误,请重试");
setDiagnosisResult({ setDiagnosisResult({
tunnelName: tunnel.name, tunnelName: tunnel.name,
@@ -627,9 +622,7 @@ export default function TunnelPage() {
// 保存到 localStorage // 保存到 localStorage
try { try {
localStorage.setItem("tunnel-order", JSON.stringify(newOrder)); localStorage.setItem("tunnel-order", JSON.stringify(newOrder));
} catch (error) { } catch {}
console.warn("无法保存隧道排序到localStorage:", error);
}
// 持久化到数据库 // 持久化到数据库
try { try {
@@ -647,8 +640,7 @@ export default function TunnelPage() {
} else { } else {
toast.error("保存排序失败:" + (response.msg || "未知错误")); toast.error("保存排序失败:" + (response.msg || "未知错误"));
} }
} catch (error) { } catch {
console.error("保存隧道排序到数据库失败:", error);
toast.error("保存排序失败,请重试"); toast.error("保存排序失败,请重试");
} }
}; };
@@ -662,6 +654,7 @@ export default function TunnelPage() {
const toggleSelect = (id: number) => { const toggleSelect = (id: number) => {
const newSet = new Set(selectedIds); const newSet = new Set(selectedIds);
if (newSet.has(id)) { if (newSet.has(id)) {
newSet.delete(id); newSet.delete(id);
} else { } else {
@@ -672,6 +665,7 @@ export default function TunnelPage() {
const selectAll = () => { const selectAll = () => {
const allIds = sortedTunnels.map((t) => t.id); const allIds = sortedTunnels.map((t) => t.id);
setSelectedIds(new Set(allIds)); setSelectedIds(new Set(allIds));
}; };
@@ -684,12 +678,16 @@ export default function TunnelPage() {
setBatchLoading(true); setBatchLoading(true);
try { try {
const res = await batchDeleteTunnels(Array.from(selectedIds)); const res = await batchDeleteTunnels(Array.from(selectedIds));
if (res.code === 0) { if (res.code === 0) {
const result = res.data; const result = res.data;
if (result.failCount === 0) { if (result.failCount === 0) {
toast.success(`成功删除 ${result.successCount} 项`); toast.success(`成功删除 ${result.successCount} 项`);
} else { } else {
toast.error(`成功 ${result.successCount} 项,失败 ${result.failCount} 项`); toast.error(
`成功 ${result.successCount} 项,失败 ${result.failCount} 项`,
);
} }
setSelectedIds(new Set()); setSelectedIds(new Set());
setSelectMode(false); setSelectMode(false);
@@ -710,12 +708,16 @@ export default function TunnelPage() {
setBatchLoading(true); setBatchLoading(true);
try { try {
const res = await batchRedeployTunnels(Array.from(selectedIds)); const res = await batchRedeployTunnels(Array.from(selectedIds));
if (res.code === 0) { if (res.code === 0) {
const result = res.data; const result = res.data;
if (result.failCount === 0) { if (result.failCount === 0) {
toast.success(`成功重新下发 ${result.successCount} 项`); toast.success(`成功重新下发 ${result.successCount} 项`);
} else { } else {
toast.error(`成功 ${result.successCount} 项,失败 ${result.failCount} 项`); toast.error(
`成功 ${result.successCount} 项,失败 ${result.failCount} 项`,
);
} }
setSelectedIds(new Set()); setSelectedIds(new Set());
setSelectMode(false); setSelectMode(false);
@@ -840,9 +842,9 @@ export default function TunnelPage() {
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Button <Button
color={selectMode ? "warning" : "default"}
size="sm" size="sm"
variant={selectMode ? "solid" : "flat"} variant={selectMode ? "solid" : "flat"}
color={selectMode ? "warning" : "default"}
onPress={toggleSelectMode} onPress={toggleSelectMode}
> >
{selectMode ? "退出" : "批量"} {selectMode ? "退出" : "批量"}
@@ -856,7 +858,9 @@ export default function TunnelPage() {
{selectMode && selectedIds.size > 0 && ( {selectMode && selectedIds.size > 0 && (
<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="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"> <div className="flex min-w-max items-center gap-2">
<span className="text-sm text-default-600 shrink-0">已选择 {selectedIds.size} 项</span> <span className="text-sm text-default-600 shrink-0">
已选择 {selectedIds.size} 项
</span>
<Button size="sm" variant="flat" onPress={selectAll}> <Button size="sm" variant="flat" onPress={selectAll}>
全选 全选
</Button> </Button>
@@ -864,19 +868,19 @@ export default function TunnelPage() {
清空 清空
</Button> </Button>
<Button <Button
size="sm"
color="danger" color="danger"
size="sm"
variant="flat" variant="flat"
onPress={() => setBatchDeleteModalOpen(true)} onPress={() => setBatchDeleteModalOpen(true)}
> >
删除 删除
</Button> </Button>
<Button <Button
size="sm"
color="primary" color="primary"
isLoading={batchLoading}
size="sm"
variant="flat" variant="flat"
onPress={handleBatchRedeploy} onPress={handleBatchRedeploy}
isLoading={batchLoading}
> >
下发 下发
</Button> </Button>
@@ -906,9 +910,9 @@ export default function TunnelPage() {
<div className="flex justify-between items-start w-full"> <div className="flex justify-between items-start w-full">
{selectMode && ( {selectMode && (
<Checkbox <Checkbox
className="mr-2"
isSelected={selectedIds.has(tunnel.id)} isSelected={selectedIds.has(tunnel.id)}
onValueChange={() => toggleSelect(tunnel.id)} onValueChange={() => toggleSelect(tunnel.id)}
className="mr-2"
/> />
)} )}
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
@@ -1666,7 +1670,7 @@ export default function TunnelPage() {
{getChainGroups().length === 0 && ( {getChainGroups().length === 0 && (
<div className="text-center py-8 bg-default-50 dark:bg-default-100/50 rounded border border-dashed border-default-300"> <div className="text-center py-8 bg-default-50 dark:bg-default-100/50 rounded border border-dashed border-default-300">
<p className="text-sm text-default-500"> <p className="text-sm text-default-500">
还没有添加转发链,点击上方"添加一跳"按钮开始添加 还没有添加转发链,点击上方&quot;添加一跳&quot;按钮开始添加
</p> </p>
</div> </div>
)} )}
@@ -1972,7 +1976,8 @@ export default function TunnelPage() {
</ModalHeader> </ModalHeader>
<ModalBody> <ModalBody>
<p> <p>
确定要删除隧道 <strong>"{tunnelToDelete?.name}"</strong> 吗? 确定要删除隧道{" "}
<strong>&quot;{tunnelToDelete?.name}&quot;</strong> 吗?
</p> </p>
<p className="text-small text-default-500"> <p className="text-small text-default-500">
此操作不可恢复,请谨慎操作。 此操作不可恢复,请谨慎操作。
@@ -2512,13 +2517,19 @@ export default function TunnelPage() {
</ModalContent> </ModalContent>
</Modal> </Modal>
<Modal isOpen={batchDeleteModalOpen} onOpenChange={setBatchDeleteModalOpen}> <Modal
isOpen={batchDeleteModalOpen}
onOpenChange={setBatchDeleteModalOpen}
>
<ModalContent> <ModalContent>
{(onClose) => ( {(onClose) => (
<> <>
<ModalHeader>确认删除</ModalHeader> <ModalHeader>确认删除</ModalHeader>
<ModalBody> <ModalBody>
<p>确定要删除选中的 {selectedIds.size} 项隧道吗?此操作不可撤销,相关转发也将被删除。</p> <p>
确定要删除选中的 {selectedIds.size}{" "}
项隧道吗?此操作不可撤销,相关转发也将被删除。
</p>
</ModalBody> </ModalBody>
<ModalFooter> <ModalFooter>
<Button variant="light" onPress={onClose}> <Button variant="light" onPress={onClose}>
@@ -2526,8 +2537,8 @@ export default function TunnelPage() {
</Button> </Button>
<Button <Button
color="danger" color="danger"
onPress={handleBatchDelete}
isLoading={batchLoading} isLoading={batchLoading}
onPress={handleBatchDelete}
> >
确认删除 确认删除
</Button> </Button>
+30 -20
View File
@@ -229,7 +229,7 @@ export default function UserPage() {
} else { } else {
toast.error(response.msg || "获取用户列表失败"); toast.error(response.msg || "获取用户列表失败");
} }
} catch (error) { } catch {
toast.error("获取用户列表失败"); toast.error("获取用户列表失败");
} finally { } finally {
setLoading(false); setLoading(false);
@@ -243,9 +243,7 @@ export default function UserPage() {
if (response.code === 0) { if (response.code === 0) {
setTunnels(response.data || []); setTunnels(response.data || []);
} }
} catch (error) { } catch {}
console.error("获取隧道列表失败:", error);
}
}; };
const loadSpeedLimits = async () => { const loadSpeedLimits = async () => {
@@ -255,9 +253,7 @@ export default function UserPage() {
if (response.code === 0) { if (response.code === 0) {
setSpeedLimits(response.data || []); setSpeedLimits(response.data || []);
} }
} catch (error) { } catch {}
console.error("获取限速规则列表失败:", error);
}
}; };
const loadUserTunnels = async (userId: number) => { const loadUserTunnels = async (userId: number) => {
@@ -270,7 +266,7 @@ export default function UserPage() {
} else { } else {
toast.error(response.msg || "获取隧道权限列表失败"); toast.error(response.msg || "获取隧道权限列表失败");
} }
} catch (error) { } catch {
toast.error("获取隧道权限列表失败"); toast.error("获取隧道权限列表失败");
} finally { } finally {
setTunnelListLoading(false); setTunnelListLoading(false);
@@ -332,7 +328,7 @@ export default function UserPage() {
} else { } else {
toast.error(response.msg || "删除失败"); toast.error(response.msg || "删除失败");
} }
} catch (error) { } catch {
toast.error("删除失败"); toast.error("删除失败");
} }
}; };
@@ -366,7 +362,7 @@ export default function UserPage() {
} else { } else {
toast.error(response.msg || (isEdit ? "更新失败" : "创建失败")); toast.error(response.msg || (isEdit ? "更新失败" : "创建失败"));
} }
} catch (error) { } catch {
toast.error(isEdit ? "更新失败" : "创建失败"); toast.error(isEdit ? "更新失败" : "创建失败");
} finally { } finally {
setUserFormLoading(false); setUserFormLoading(false);
@@ -406,7 +402,7 @@ export default function UserPage() {
} else { } else {
toast.error(response.msg || "分配失败"); toast.error(response.msg || "分配失败");
} }
} catch (error) { } catch {
toast.error("分配失败"); toast.error("分配失败");
} finally { } finally {
setAssignLoading(false); setAssignLoading(false);
@@ -445,7 +441,7 @@ export default function UserPage() {
} else { } else {
toast.error(response.msg || "更新失败"); toast.error(response.msg || "更新失败");
} }
} catch (error) { } catch {
toast.error("更新失败"); toast.error("更新失败");
} finally { } finally {
setEditTunnelLoading(false); setEditTunnelLoading(false);
@@ -473,7 +469,7 @@ export default function UserPage() {
} else { } else {
toast.error(response.msg || "删除失败"); toast.error(response.msg || "删除失败");
} }
} catch (error) { } catch {
toast.error("删除失败"); toast.error("删除失败");
} }
}; };
@@ -502,7 +498,7 @@ export default function UserPage() {
} else { } else {
toast.error(response.msg || "重置失败"); toast.error(response.msg || "重置失败");
} }
} catch (error) { } catch {
toast.error("重置失败"); toast.error("重置失败");
} finally { } finally {
setResetFlowLoading(false); setResetFlowLoading(false);
@@ -535,7 +531,7 @@ export default function UserPage() {
} else { } else {
toast.error(response.msg || "重置失败"); toast.error(response.msg || "重置失败");
} }
} catch (error) { } catch {
toast.error("重置失败"); toast.error("重置失败");
} finally { } finally {
setResetTunnelFlowLoading(false); setResetTunnelFlowLoading(false);
@@ -1005,6 +1001,7 @@ export default function UserPage() {
return ( return (
<div <div
key={tunnel.id} key={tunnel.id}
aria-disabled={isAssigned}
className={` className={`
px-4 py-3 rounded-lg border transition-all duration-200 cursor-pointer px-4 py-3 rounded-lg border transition-all duration-200 cursor-pointer
${ ${
@@ -1015,9 +1012,21 @@ export default function UserPage() {
: "bg-white dark:bg-default-50 border-default-200 dark:border-default-100/30 hover:border-primary-200 dark:hover:border-primary-500/30 hover:shadow-sm" : "bg-white dark:bg-default-50 border-default-200 dark:border-default-100/30 hover:border-primary-200 dark:hover:border-primary-500/30 hover:shadow-sm"
} }
`} `}
role="button"
tabIndex={isAssigned ? -1 : 0}
onClick={() => onClick={() =>
!isAssigned && toggleTunnelSelection(tunnel.id) !isAssigned && toggleTunnelSelection(tunnel.id)
} }
onKeyDown={(event) => {
if (isAssigned) {
return;
}
if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
toggleTunnelSelection(tunnel.id);
}
}}
> >
<div className="flex items-center justify-between gap-4"> <div className="flex items-center justify-between gap-4">
<div className="flex items-center gap-3 flex-1 min-w-0"> <div className="flex items-center gap-3 flex-1 min-w-0">
@@ -1051,7 +1060,7 @@ export default function UserPage() {
</div> </div>
{isSelected && !isAssigned && ( {isSelected && !isAssigned && (
<div onClick={(e) => e.stopPropagation()}> <div>
<Select <Select
className="w-36" className="w-36"
classNames={{ classNames={{
@@ -1071,6 +1080,7 @@ export default function UserPage() {
: ["null"] : ["null"]
} }
size="sm" size="sm"
onClick={(e) => e.stopPropagation()}
onSelectionChange={(keys) => { onSelectionChange={(keys) => {
const value = Array.from(keys)[0] as string; const value = Array.from(keys)[0] as string;
@@ -1455,7 +1465,7 @@ export default function UserPage() {
<p className="text-foreground"> <p className="text-foreground">
确定要删除用户{" "} 确定要删除用户{" "}
<span className="font-semibold text-danger"> <span className="font-semibold text-danger">
"{userToDelete?.user}" &quot;{userToDelete?.user}&quot;
</span>{" "} </span>{" "}
吗? 吗?
</p> </p>
@@ -1500,7 +1510,7 @@ export default function UserPage() {
<span className="font-semibold">{currentUser?.user}</span>{" "} <span className="font-semibold">{currentUser?.user}</span>{" "}
对隧道{" "} 对隧道{" "}
<span className="font-semibold text-danger"> <span className="font-semibold text-danger">
"{tunnelToDelete?.tunnelName}" &quot;{tunnelToDelete?.tunnelName}&quot;
</span>{" "} </span>{" "}
的权限吗? 的权限吗?
</p> </p>
@@ -1553,7 +1563,7 @@ export default function UserPage() {
<p className="text-foreground"> <p className="text-foreground">
确定要重置用户{" "} 确定要重置用户{" "}
<span className="font-semibold text-warning"> <span className="font-semibold text-warning">
"{userToReset?.user}" &quot;{userToReset?.user}&quot;
</span>{" "} </span>{" "}
的流量吗? 的流量吗?
</p> </p>
@@ -1643,7 +1653,7 @@ export default function UserPage() {
<span className="font-semibold">{currentUser?.user}</span>{" "} <span className="font-semibold">{currentUser?.user}</span>{" "}
对隧道{" "} 对隧道{" "}
<span className="font-semibold text-warning"> <span className="font-semibold text-warning">
"{tunnelToReset?.tunnelName}" &quot;{tunnelToReset?.tunnelName}&quot;
</span>{" "} </span>{" "}
的流量吗? 的流量吗?
</p> </p>
+8 -5
View File
@@ -2,7 +2,7 @@
declare global { declare global {
interface Window { interface Window {
TAC: typeof TAC; TAC: TACConstructor;
currentCaptcha?: any; currentCaptcha?: any;
currentCaptchaRes?: any; currentCaptchaRes?: any;
} }
@@ -42,13 +42,16 @@ interface CaptchaStyle {
moveTrackMaskBorderColor?: string; moveTrackMaskBorderColor?: string;
} }
declare class TAC { interface TACInstance {
constructor(config: CaptchaConfig, style?: CaptchaStyle); init(): TACInstance;
init(): TAC;
reloadCaptcha(): void; reloadCaptcha(): void;
destroyWindow(): void; destroyWindow(): void;
openCaptcha(): void; openCaptcha(): void;
} }
type TACConstructor = new (
config: CaptchaConfig,
style?: CaptchaStyle,
) => TACInstance;
export {}; export {};
+2 -4
View File
@@ -60,17 +60,15 @@ export function isLoggedIn(): boolean {
/** /**
* 权限检查装饰器函数 * 权限检查装饰器函数
* @param fn 要执行的函数 * @param fn 要执行的函数
* @param errorMsg 权限不足时的错误提示 * @param _errorMsg 权限不足时的错误提示
* @returns 包装后的函数 * @returns 包装后的函数
*/ */
export function requireAdmin<T extends (...args: any[]) => any>( export function requireAdmin<T extends (...args: any[]) => any>(
fn: T, fn: T,
errorMsg: string = "权限不足,仅管理员可操作", _errorMsg: string = "权限不足,仅管理员可操作",
): T { ): T {
return ((...args: Parameters<T>) => { return ((...args: Parameters<T>) => {
if (!isAdmin()) { if (!isAdmin()) {
console.warn(errorMsg);
return false; return false;
} }
+1 -1
View File
@@ -27,7 +27,7 @@ function getPayloadFromToken(token: string): JWTPayload | null {
const decodedPayload = atob(encodedPayload); const decodedPayload = atob(encodedPayload);
return JSON.parse(decodedPayload) as JWTPayload; return JSON.parse(decodedPayload) as JWTPayload;
} catch (error) { } catch {
return null; return null;
} }
} }
+1 -1
View File
@@ -119,7 +119,7 @@ export function validatePanelAddress(address: string): boolean {
} }
return false; return false;
} catch (error) { } catch {
// URL构造函数失败说明格式不正确 // URL构造函数失败说明格式不正确
return false; return false;
} }