Files
clash-proxy/src/components/proxy/use-render-list.ts
Junkai W. f2073a2f83 Add Func 链式代理 (#4624)
* 添加链式代理gui和语言支持
在Iruntime中添跟新链式代理配置方法
同时添加了cmd

* 修复读取运行时代理链配置文件bug

* t

* 完成链式代理配置构造

* 修复获取链式代理运行时配置的bug

* 完整的链式代理功能
2025-09-15 07:44:54 +08:00

323 lines
8.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useEffect, useMemo } from "react";
import { useVerge } from "@/hooks/use-verge";
import { filterSort } from "./use-filter-sort";
import { useWindowWidth } from "./use-window-width";
import {
useHeadStateNew,
DEFAULT_STATE,
type HeadState,
} from "./use-head-state";
import { useAppData } from "@/providers/app-data-provider";
import useSWR from "swr";
import { getRuntimeConfig } from "@/services/cmds";
import delayManager from "@/services/delay";
// 定义代理项接口
interface IProxyItem {
name: string;
type: string;
udp: boolean;
xudp: boolean;
tfo: boolean;
mptcp: boolean;
smux: boolean;
history: {
time: string;
delay: number;
}[];
provider?: string;
testUrl?: string;
[key: string]: any; // 添加索引签名以适应其他可能的属性
}
// 代理组类型
type ProxyGroup = {
name: string;
type: string;
udp: boolean;
xudp: boolean;
tfo: boolean;
mptcp: boolean;
smux: boolean;
history: {
time: string;
delay: number;
}[];
now: string;
all: IProxyItem[];
hidden?: boolean;
icon?: string;
testUrl?: string;
provider?: string;
};
export interface IRenderItem {
// 组 | head | item | empty | item col
type: 0 | 1 | 2 | 3 | 4;
key: string;
group: ProxyGroup;
proxy?: IProxyItem;
col?: number;
proxyCol?: IProxyItem[];
headState?: HeadState;
// 新增支持图标和其他元数据
icon?: string;
provider?: string;
testUrl?: string;
}
// 优化列布局计算
const calculateColumns = (width: number, configCol: number): number => {
if (configCol > 0 && configCol < 6) return configCol;
if (width > 1920) return 5;
if (width > 1450) return 4;
if (width > 1024) return 3;
if (width > 900) return 2;
if (width >= 600) return 2;
return 1;
};
// 优化分组逻辑
const groupProxies = <T = any>(list: T[], size: number): T[][] => {
return list.reduce((acc, item) => {
const lastGroup = acc[acc.length - 1];
if (!lastGroup || lastGroup.length >= size) {
acc.push([item]);
} else {
lastGroup.push(item);
}
return acc;
}, [] as T[][]);
};
export const useRenderList = (mode: string, isChainMode?: boolean) => {
// 使用全局数据提供者
const { proxies: proxiesData, refreshProxy } = useAppData();
const { verge } = useVerge();
const { width } = useWindowWidth();
const [headStates, setHeadState] = useHeadStateNew();
// 获取运行时配置用于链式代理模式
const { data: runtimeConfig } = useSWR(
isChainMode ? "getRuntimeConfig" : null,
getRuntimeConfig,
{
revalidateOnFocus: false,
revalidateIfStale: true,
},
);
// 计算列数
const col = useMemo(
() => calculateColumns(width, verge?.proxy_layout_column || 6),
[width, verge?.proxy_layout_column],
);
// 确保代理数据加载
useEffect(() => {
if (!proxiesData) return;
const { groups, proxies } = proxiesData;
if (
(mode === "rule" && !groups.length) ||
(mode === "global" && proxies.length < 2)
) {
const handle = setTimeout(() => refreshProxy(), 500);
return () => clearTimeout(handle);
}
}, [proxiesData, mode, refreshProxy]);
// 链式代理模式节点自动计算延迟
useEffect(() => {
if (!isChainMode || !runtimeConfig) return;
const allProxies: IProxyItem[] = Object.values(
(runtimeConfig as any).proxies || {},
);
if (allProxies.length === 0) return;
// 设置组监听器,当有延迟更新时自动刷新
const groupListener = () => {
console.log("[ChainMode] 延迟更新刷新UI");
refreshProxy();
};
delayManager.setGroupListener("chain-mode", groupListener);
const calculateDelays = async () => {
try {
const timeout = verge?.default_latency_timeout || 10000;
const proxyNames = allProxies.map((proxy) => proxy.name);
console.log(`[ChainMode] 开始计算 ${proxyNames.length} 个节点的延迟`);
// 使用 delayManager 计算延迟,每个节点计算完成后会自动触发监听器刷新界面
delayManager.checkListDelay(proxyNames, "chain-mode", timeout);
} catch (error) {
console.error("Failed to calculate delays for chain mode:", error);
}
};
// 延迟执行避免阻塞
const handle = setTimeout(calculateDelays, 100);
return () => {
clearTimeout(handle);
// 清理组监听器
delayManager.removeGroupListener("chain-mode");
};
}, [
isChainMode,
runtimeConfig,
verge?.default_latency_timeout,
refreshProxy,
]);
// 处理渲染列表
const renderList: IRenderItem[] = useMemo(() => {
if (!proxiesData) return [];
// 链式代理模式下,从运行时配置读取所有 proxies
if (isChainMode && runtimeConfig) {
// 从运行时配置直接获取 proxies 列表 (需要类型断言)
const allProxies: IProxyItem[] = Object.values(
(runtimeConfig as any).proxies || {},
);
// 为每个节点获取延迟信息
const proxiesWithDelay = allProxies.map((proxy) => {
const delay = delayManager.getDelay(proxy.name, "chain-mode");
return {
...proxy,
// 如果delayManager有延迟数据更新history
history:
delay >= 0
? [{ time: new Date().toISOString(), delay }]
: proxy.history || [],
};
});
// 创建一个虚拟的组来容纳所有节点
const virtualGroup: ProxyGroup = {
name: "All Proxies",
type: "Selector",
udp: false,
xudp: false,
tfo: false,
mptcp: false,
smux: false,
history: [],
now: "",
all: proxiesWithDelay,
};
// 返回节点列表(不显示组头)
if (col > 1) {
return groupProxies(proxiesWithDelay, col).map(
(proxyCol, colIndex) => ({
type: 4,
key: `chain-col-${colIndex}`,
group: virtualGroup,
headState: DEFAULT_STATE,
col,
proxyCol,
provider: proxyCol[0]?.provider,
}),
);
} else {
return proxiesWithDelay.map((proxy) => ({
type: 2,
key: `chain-${proxy.name}`,
group: virtualGroup,
proxy,
headState: DEFAULT_STATE,
provider: proxy.provider,
}));
}
}
// 正常模式的渲染逻辑
const useRule = mode === "rule" || mode === "script";
const renderGroups =
useRule && proxiesData.groups.length
? proxiesData.groups
: [proxiesData.global!];
const retList = renderGroups.flatMap((group: ProxyGroup) => {
const headState = headStates[group.name] || DEFAULT_STATE;
const ret: IRenderItem[] = [
{
type: 0,
key: group.name,
group,
headState,
icon: group.icon,
testUrl: group.testUrl,
},
];
if (headState?.open || !useRule) {
const proxies = filterSort(
group.all,
group.name,
headState.filterText,
headState.sortType,
);
ret.push({
type: 1,
key: `head-${group.name}`,
group,
headState,
});
if (!proxies.length) {
ret.push({
type: 3,
key: `empty-${group.name}`,
group,
headState,
});
} else if (col > 1) {
return ret.concat(
groupProxies(proxies, col).map((proxyCol, colIndex) => ({
type: 4,
key: `col-${group.name}-${proxyCol[0].name}-${colIndex}`,
group,
headState,
col,
proxyCol,
provider: proxyCol[0].provider,
})),
);
} else {
return ret.concat(
proxies.map((proxy) => ({
type: 2,
key: `${group.name}-${proxy!.name}`,
group,
proxy,
headState,
provider: proxy.provider,
})),
);
}
}
return ret;
});
if (!useRule) return retList.slice(1);
return retList.filter((item: IRenderItem) => !item.group.hidden);
}, [headStates, proxiesData, mode, col, isChainMode, runtimeConfig]);
return {
renderList,
onProxies: refreshProxy,
onHeadState: setHeadState,
currentColumns: col,
};
};
// 优化建议:如有大数据量,建议用虚拟滚动(已在 ProxyGroups 组件中实现),此处无需额外处理。