diff --git a/src/components/proxy/proxy-group-navigator.tsx b/src/components/proxy/proxy-group-navigator.tsx
new file mode 100644
index 00000000..4999da49
--- /dev/null
+++ b/src/components/proxy/proxy-group-navigator.tsx
@@ -0,0 +1,94 @@
+import { Box, Button, Tooltip } from "@mui/material";
+import { useCallback, useMemo } from "react";
+
+interface ProxyGroupNavigatorProps {
+ proxyGroupNames: string[];
+ onGroupLocation: (groupName: string) => void;
+}
+
+// 提取代理组名的第一个字符
+const getGroupDisplayChar = (groupName: string): string => {
+ if (!groupName) return "?";
+
+ // 直接返回第一个字符,支持表情符号
+ const firstChar = Array.from(groupName)[0];
+ return firstChar || "?";
+};
+
+export const ProxyGroupNavigator = ({
+ proxyGroupNames,
+ onGroupLocation,
+}: ProxyGroupNavigatorProps) => {
+ const handleGroupClick = useCallback(
+ (groupName: string) => {
+ onGroupLocation(groupName);
+ },
+ [onGroupLocation],
+ );
+
+ // 处理代理组数据,去重和排序
+ const processedGroups = useMemo(() => {
+ return proxyGroupNames
+ .filter((name) => name && name.trim())
+ .map((name) => ({
+ name,
+ displayChar: getGroupDisplayChar(name),
+ }));
+ }, [proxyGroupNames]);
+
+ if (processedGroups.length === 0) {
+ return null;
+ }
+
+ return (
+
+ {processedGroups.map(({ name, displayChar }) => (
+
+
+
+ ))}
+
+ );
+};
diff --git a/src/components/proxy/proxy-groups.tsx b/src/components/proxy/proxy-groups.tsx
index 889e66b0..476f5f90 100644
--- a/src/components/proxy/proxy-groups.tsx
+++ b/src/components/proxy/proxy-groups.tsx
@@ -10,10 +10,11 @@ import {
Menu,
MenuItem,
Divider,
+ Button,
} from "@mui/material";
import { ArchiveOutlined, ExpandMoreRounded } from "@mui/icons-material";
import { useLockFn } from "ahooks";
-import { useRef, useState, useEffect, useCallback } from "react";
+import { useRef, useState, useEffect, useCallback, useMemo } from "react";
import useSWR from "swr";
import { useTranslation } from "react-i18next";
import { Virtuoso, type VirtuosoHandle } from "react-virtuoso";
@@ -25,6 +26,7 @@ import {
providerHealthCheck,
getGroupProxyDelays,
updateProxyChainConfigInRuntime,
+ getRuntimeConfig,
} from "@/services/cmds";
import delayManager from "@/services/delay";
@@ -33,6 +35,7 @@ import { ScrollTopButton } from "../layout/scroll-top-button";
import { ProxyChain } from "./proxy-chain";
import { ProxyRender } from "./proxy-render";
+import { ProxyGroupNavigator } from "./proxy-group-navigator";
import { useRenderList } from "./use-render-list";
interface Props {
@@ -326,6 +329,45 @@ export const ProxyGroups = (props: Props) => {
}
};
+ // 获取运行时配置
+ const { data: runtimeConfig } = useSWR("getRuntimeConfig", getRuntimeConfig, {
+ revalidateOnFocus: false,
+ revalidateIfStale: true,
+ });
+
+ // 获取所有代理组名称
+ const getProxyGroupNames = useCallback(() => {
+ const config = runtimeConfig as any;
+ if (!config?.["proxy-groups"]) return [];
+
+ return config["proxy-groups"]
+ .map((group: any) => group.name)
+ .filter((name: string) => name && name.trim() !== "");
+ }, [runtimeConfig]);
+
+ // 定位到指定的代理组
+ const handleGroupLocationByName = useCallback(
+ (groupName: string) => {
+ const index = renderList.findIndex(
+ (item) => item.type === 0 && item.group?.name === groupName,
+ );
+
+ if (index >= 0) {
+ virtuosoRef.current?.scrollToIndex?.({
+ index,
+ align: "start",
+ behavior: "smooth",
+ });
+ }
+ },
+ [renderList],
+ );
+
+ const proxyGroupNames = useMemo(
+ () => getProxyGroupNames(),
+ [getProxyGroupNames],
+ );
+
if (mode === "direct") {
return ;
}
@@ -522,6 +564,14 @@ export const ProxyGroups = (props: Props) => {
+ {/* 代理组导航栏 */}
+ {mode === "rule" && (
+
+ )}
+