mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-01-29 00:35:38 +08:00
247 lines
6.3 KiB
TypeScript
247 lines
6.3 KiB
TypeScript
import { useTranslation } from "react-i18next";
|
|
import {
|
|
Box,
|
|
Typography,
|
|
Stack,
|
|
Paper,
|
|
Tooltip,
|
|
alpha,
|
|
useTheme,
|
|
Fade,
|
|
} from "@mui/material";
|
|
import { useState, useMemo, memo, FC } from "react";
|
|
import ProxyControlSwitches from "@/components/shared/ProxyControlSwitches";
|
|
import { Notice } from "@/components/base";
|
|
import {
|
|
ComputerRounded,
|
|
TroubleshootRounded,
|
|
HelpOutlineRounded,
|
|
SvgIconComponent,
|
|
} from "@mui/icons-material";
|
|
import { useVerge } from "@/hooks/use-verge";
|
|
import { useSystemState } from "@/hooks/use-system-state";
|
|
|
|
const LOCAL_STORAGE_TAB_KEY = "clash-verge-proxy-active-tab";
|
|
|
|
interface TabButtonProps {
|
|
isActive: boolean;
|
|
onClick: () => void;
|
|
icon: SvgIconComponent;
|
|
label: string;
|
|
hasIndicator?: boolean;
|
|
}
|
|
|
|
// 抽取Tab组件以减少重复代码
|
|
const TabButton: FC<TabButtonProps> = memo(
|
|
({ isActive, onClick, icon: Icon, label, hasIndicator = false }) => (
|
|
<Paper
|
|
elevation={isActive ? 2 : 0}
|
|
onClick={onClick}
|
|
sx={{
|
|
cursor: "pointer",
|
|
px: 2,
|
|
py: 1,
|
|
display: "flex",
|
|
alignItems: "center",
|
|
justifyContent: "center",
|
|
gap: 1,
|
|
bgcolor: isActive ? "primary.main" : "background.paper",
|
|
color: isActive ? "primary.contrastText" : "text.primary",
|
|
borderRadius: 1.5,
|
|
flex: 1,
|
|
maxWidth: 160,
|
|
transition: "all 0.2s ease-in-out",
|
|
position: "relative",
|
|
"&:hover": {
|
|
transform: "translateY(-1px)",
|
|
boxShadow: 1,
|
|
},
|
|
"&:after": isActive
|
|
? {
|
|
content: '""',
|
|
position: "absolute",
|
|
bottom: -9,
|
|
left: "50%",
|
|
width: 2,
|
|
height: 9,
|
|
bgcolor: "primary.main",
|
|
transform: "translateX(-50%)",
|
|
}
|
|
: {},
|
|
}}
|
|
>
|
|
<Icon fontSize="small" />
|
|
<Typography variant="body2" sx={{ fontWeight: isActive ? 600 : 400 }}>
|
|
{label}
|
|
</Typography>
|
|
{hasIndicator && (
|
|
<Box
|
|
sx={{
|
|
width: 8,
|
|
height: 8,
|
|
borderRadius: "50%",
|
|
bgcolor: isActive ? "#fff" : "success.main",
|
|
position: "absolute",
|
|
top: 8,
|
|
right: 8,
|
|
}}
|
|
/>
|
|
)}
|
|
</Paper>
|
|
),
|
|
);
|
|
|
|
interface TabDescriptionProps {
|
|
description: string;
|
|
tooltipTitle: string;
|
|
}
|
|
|
|
// 抽取描述文本组件
|
|
const TabDescription: FC<TabDescriptionProps> = memo(
|
|
({ description, tooltipTitle }) => (
|
|
<Fade in={true} timeout={200}>
|
|
<Typography
|
|
variant="caption"
|
|
component="div"
|
|
sx={{
|
|
width: "95%",
|
|
textAlign: "center",
|
|
color: "text.secondary",
|
|
p: 0.8,
|
|
borderRadius: 1,
|
|
borderColor: "primary.main",
|
|
borderWidth: 1,
|
|
borderStyle: "solid",
|
|
backgroundColor: "background.paper",
|
|
display: "flex",
|
|
alignItems: "center",
|
|
justifyContent: "center",
|
|
gap: 0.5,
|
|
wordBreak: "break-word",
|
|
hyphens: "auto",
|
|
}}
|
|
>
|
|
{description}
|
|
<Tooltip title={tooltipTitle}>
|
|
<HelpOutlineRounded
|
|
sx={{ fontSize: 14, opacity: 0.7, flexShrink: 0 }}
|
|
/>
|
|
</Tooltip>
|
|
</Typography>
|
|
</Fade>
|
|
),
|
|
);
|
|
|
|
export const ProxyTunCard: FC = () => {
|
|
const { t } = useTranslation();
|
|
const theme = useTheme();
|
|
const [activeTab, setActiveTab] = useState<string>(
|
|
() => localStorage.getItem(LOCAL_STORAGE_TAB_KEY) || "system",
|
|
);
|
|
|
|
// 获取代理状态信息
|
|
const { verge } = useVerge();
|
|
const { isSidecarMode, isAdminMode } = useSystemState();
|
|
|
|
// 从verge配置中获取开关状态
|
|
const { enable_system_proxy, enable_tun_mode } = verge ?? {};
|
|
|
|
// 判断Tun模式是否可用 - 当处于服务模式或管理员模式时可用
|
|
const isTunAvailable = !isSidecarMode || isAdminMode;
|
|
|
|
// 处理错误
|
|
const handleError = (err: Error) => {
|
|
Notice.error(err.message || err.toString(), 3000);
|
|
};
|
|
|
|
// 处理标签切换并保存到localStorage
|
|
const handleTabChange = (tab: string) => {
|
|
setActiveTab(tab);
|
|
localStorage.setItem(LOCAL_STORAGE_TAB_KEY, tab);
|
|
};
|
|
|
|
// 用户提示文本 - 使用useMemo避免重复计算
|
|
const tabDescription = useMemo(() => {
|
|
if (activeTab === "system") {
|
|
return {
|
|
text: enable_system_proxy
|
|
? t("System Proxy Enabled")
|
|
: t("System Proxy Disabled"),
|
|
tooltip: t("System Proxy Info"),
|
|
};
|
|
} else {
|
|
return {
|
|
text: !isTunAvailable
|
|
? t("TUN Mode Service Required")
|
|
: enable_tun_mode
|
|
? t("TUN Mode Enabled")
|
|
: t("TUN Mode Disabled"),
|
|
tooltip: t("TUN Mode Intercept Info"),
|
|
};
|
|
}
|
|
}, [activeTab, enable_system_proxy, enable_tun_mode, isTunAvailable, t]);
|
|
|
|
return (
|
|
<Box sx={{ display: "flex", flexDirection: "column", width: "100%" }}>
|
|
{/* 选项卡 */}
|
|
<Stack
|
|
direction="row"
|
|
spacing={1}
|
|
sx={{
|
|
display: "flex",
|
|
justifyContent: "center",
|
|
position: "relative",
|
|
zIndex: 2,
|
|
}}
|
|
>
|
|
<TabButton
|
|
isActive={activeTab === "system"}
|
|
onClick={() => handleTabChange("system")}
|
|
icon={ComputerRounded}
|
|
label={t("System Proxy")}
|
|
hasIndicator={enable_system_proxy}
|
|
/>
|
|
<TabButton
|
|
isActive={activeTab === "tun"}
|
|
onClick={() => handleTabChange("tun")}
|
|
icon={TroubleshootRounded}
|
|
label={t("Tun Mode")}
|
|
hasIndicator={enable_tun_mode && isTunAvailable}
|
|
/>
|
|
</Stack>
|
|
|
|
{/* 说明文本区域 */}
|
|
<Box
|
|
sx={{
|
|
width: "100%",
|
|
my: 1,
|
|
position: "relative",
|
|
display: "flex",
|
|
justifyContent: "center",
|
|
overflow: "visible",
|
|
}}
|
|
>
|
|
<TabDescription
|
|
description={tabDescription.text}
|
|
tooltipTitle={tabDescription.tooltip}
|
|
/>
|
|
</Box>
|
|
|
|
{/* 控制开关部分 */}
|
|
<Box
|
|
sx={{
|
|
mt: 0,
|
|
p: 1,
|
|
bgcolor: alpha(theme.palette.primary.main, 0.04),
|
|
borderRadius: 2,
|
|
}}
|
|
>
|
|
<ProxyControlSwitches
|
|
onError={handleError}
|
|
label={activeTab === "system" ? t("System Proxy") : t("Tun Mode")}
|
|
/>
|
|
</Box>
|
|
</Box>
|
|
);
|
|
};
|