feat:新增mockData,以及一些逻辑的修复

This commit is contained in:
liyuanhu 2025-04-16 14:31:14 +08:00
parent ae04f35bb1
commit da91340bcf
8 changed files with 3336 additions and 2849 deletions

View File

@ -0,0 +1,3 @@
<svg width="218" height="148" viewBox="0 0 218 148" fill="none" xmlns="http://www.w3.org/2000/svg">
<path id="Vector 330" d="M0.855469 0.683594L95.828 147.041H217.718" stroke="white" stroke-width="0.458267" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 239 B

View File

@ -6,42 +6,49 @@ import type { EChartsType } from "echarts";
import worldGeoJson from "@/assets/echarts-map/json/world.json";
import { geoCoordMap, countryNameMap, countryCodeMap } from "@/data";
import { getUrl } from "@/lib/utils";
import { CONST_TOOLTIP_TYPE } from "..";
import { CONST_TOOLTIP_TYPE } from "@/pages/anti-forensics-forwarding";
const planePathImg =
"image://data:image/svg+xml;charset=utf-8;base64,PHN2ZyB3aWR0aD0iNjciIGhlaWdodD0iMTAyIiB2aWV3Qm94PSIwIDAgNjcgMTAyIiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8ZyBmaWx0ZXI9InVybCgjZmlsdGVyMF9mXzYxMTdfMjEyNDA3KSI+CjxwYXRoIGQ9Ik0zNC4yMTA5IDkxLjE4ODZMNTMuNjU3OCA0MC45NThDNTQuOTM4IDM3LjY1MTMgNTUuNzk4MyAzNC4xNTkyIDU1LjM1NjMgMzAuNjQxQzU0LjQzNTcgMjMuMzEyOCA1MC40Njg0IDExLjAyMDggMzQuMjExMiAxMS4wMjA4QzE5LjE5MDMgMTEuMDIwOCAxMy45MTEgMjEuNTE0NiAxMi4wNTU0IDI4Ljg5MTJDMTAuOTAxIDMzLjQ4MDYgMTEuOTkyNiAzOC4yMTg2IDEzLjgyMzEgNDIuNTgyN0wzNC4yMTA5IDkxLjE4ODZaIiBmaWxsPSJ1cmwoI3BhaW50MF9saW5lYXJfNjExN18yMTI0MDcpIi8+CjwvZz4KPGRlZnM+CjxmaWx0ZXIgaWQ9ImZpbHRlcjBfZl82MTE3XzIxMjQwNyIgeD0iMC44OTE3NDQiIHk9IjAuMzMxOTkiIHdpZHRoPSI2NS4yNzA3IiBoZWlnaHQ9IjEwMS41NDUiIGZpbHRlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIj4KPGZlRmxvb2QgZmxvb2Qtb3BhY2l0eT0iMCIgcmVzdWx0PSJCYWNrZ3JvdW5kSW1hZ2VGaXgiLz4KPGZlQmxlbmQgbW9kZT0ibm9ybWFsIiBpbj0iU291cmNlR3JhcGhpYyIgaW4yPSJCYWNrZ3JvdW5kSW1hZ2VGaXgiIHJlc3VsdD0ic2hhcGUiLz4KPGZlR2F1c3NpYW5CbHVyIHN0ZERldmlhdGlvbj0iNS4zNDQ0MSIgcmVzdWx0PSJlZmZlY3QxX2ZvcmVncm91bmRCbHVyXzYxMTdfMjEyNDA3Ii8+CjwvZmlsdGVyPgo8bGluZWFyR3JhZGllbnQgaWQ9InBhaW50MF9saW5lYXJfNjExN18yMTI0MDciIHgxPSIzNS4yODI2IiB5MT0iMTAuODU2NCIgeDI9IjM1LjI4MjYiIHkyPSI4Ni44NTY0IiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CjxzdG9wIHN0b3AtY29sb3I9IiMwMEYyRkYiLz4KPHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjMTUwMEZGIi8+CjwvbGluZWFyR3JhZGllbnQ+CjwvZGVmcz4KPC9zdmc+Cg==";
interface LinesItemType {
name: string;
country_code: string;
value: number[];
color?: string; // 添加颜色属性
}
type LinesDataType = [LinesItemType, LinesItemType];
type LinesType = [string, LinesDataType[]];
// 创建单个国家的涟漪效果
const createCountryRipple = (countryCode: string) => {
const createCountryRipple = (countryCode: string, color?: string) => {
const coords = geoCoordMap[countryCode];
if (!coords) return null;
return {
name: countryCodeMap[countryCode] ?? "",
value: coords,
country_code: countryCode,
color: color || "#0ea5e9", // 添加颜色属性,如果没有则使用默认颜色
};
};
export const WorldGeo = memo(
({
screenData,
currentValue,
newHomeProxies,
selectedApp,
tooltipType,
tooltipClosed,
setTooltipClosed,
}: {
screenData: any;
currentValue: any;
newHomeProxies: any;
selectedApp: any;
tooltipType: string;
tooltipClosed: boolean;
setTooltipClosed: (value: boolean) => void;
}) => {
// const queryClient = useQueryClient()
// 嵌套加密ref
const customTooltipRef = useRef<HTMLDivElement | null>(null);
// 流量混淆ref
const customTooltip2Ref = useRef<HTMLDivElement | null>(null);
const proxyGeoRef = useRef<EChartsType | null>(null);
const preMainToData = useRef<{ country_code: string }[]>([]);
const lineMidpointsRef = useRef<
@ -54,13 +61,9 @@ export const WorldGeo = memo(
>([]);
const labelContainerRef = useRef<HTMLDivElement | null>(null);
const labelsRef = useRef<HTMLDivElement[]>([]);
const mainToData = useMemo(() => {
// 使用新的数据结构
const proxiesList =
selectedApp && selectedApp?.jumpList
? [selectedApp.jumpList]
: screenData?.proxy_info?.proxies ?? [];
const proxiesList = currentValue ?? [];
// 初始化数据数组 - 不再包含 startCountry
const data: any = [];
// 遍历代理列表
@ -76,25 +79,28 @@ export const WorldGeo = memo(
country_code: item.country_code,
type: "start",
isLine: proxyItem.isLine, // 保存连线标志
color: proxyItem.color || item.color, // 添加颜色属性
});
// 添加终点ingress_country_code
data.push({
country_code: item.ingress_country_code,
type: "end",
isLine: proxyItem.isLine, // 保存连线标志
color: proxyItem.color || item.color, // 添加颜色属性
});
} else {
// 如果没有 ingress_country_code只添加 country_code
data.push({
country_code: item.country_code,
isLine: proxyItem.isLine, // 保存连线标志
color: proxyItem.color || item.color, // 添加颜色属性
});
}
});
}
});
return data;
}, [screenData, selectedApp]);
}, [currentValue]);
// 创建自定义提示框DOM元素
const createCustomTooltip = () => {
// 如果已经存在自定义提示框,则移除它
@ -110,9 +116,8 @@ export const WorldGeo = memo(
tooltip.style.backgroundColor = "transparent";
// 设置提示框内容
const currentTooltipType =
CONST_TOOLTIP_TYPE[
tooltipType as keyof typeof CONST_TOOLTIP_TYPE
] || CONST_TOOLTIP_TYPE.NESTED_ENCRYPTION;
CONST_TOOLTIP_TYPE[tooltipType as keyof typeof CONST_TOOLTIP_TYPE] ||
CONST_TOOLTIP_TYPE.NESTED_ENCRYPTION;
tooltip.innerHTML = `
<div class="tooltip-content">
<img class="line-img" src="${getUrl(
@ -136,8 +141,7 @@ export const WorldGeo = memo(
? "encryption-img"
: "traffic-obfuscation-img"
}" src="${getUrl(
CONST_TOOLTIP_TYPE.NESTED_ENCRYPTION.type ===
currentTooltipType.type
CONST_TOOLTIP_TYPE.NESTED_ENCRYPTION.type === currentTooltipType.type
? "image/nested-encryption.png"
: "image/traffic-obfuscation.png"
)}" alt="" />
@ -164,25 +168,94 @@ export const WorldGeo = memo(
const positionCustomTooltip = () => {
if (!customTooltipRef.current || !proxyGeoRef.current) return;
// 找到US点
const coords = geoCoordMap["CA"];
const coords = geoCoordMap["GL"];
if (!coords) return;
try {
// 将地理坐标转换为屏幕坐标
const screenCoord = proxyGeoRef.current.convertToPixel(
"geo",
coords
);
const screenCoord = proxyGeoRef.current.convertToPixel("geo", coords);
if (
screenCoord &&
Array.isArray(screenCoord) &&
screenCoord.length === 2
) {
// 设置提示框位置
customTooltipRef.current.style.left = `${
screenCoord[0] + 232 + 7
customTooltipRef.current.style.left = `${screenCoord[0] + 232 + 7}px`;
customTooltipRef.current.style.top = `${screenCoord[1] + 40 - 190}px`;
}
} catch (error) {
console.error("Error positioning tooltip:", error);
}
};
// 创建自定义提示框DOM元素
const createCustomTooltip2 = () => {
// 如果已经存在自定义提示框,则移除它
if (document.getElementById("custom-fixed-tooltip2")) {
document.getElementById("custom-fixed-tooltip2")?.remove();
}
// 创建自定义提示框
const tooltip = document.createElement("div");
tooltip.id = "custom-fixed-tooltip2";
tooltip.style.position = "fixed";
tooltip.style.zIndex = "1000";
tooltip.style.pointerEvents = "auto";
tooltip.style.backgroundColor = "transparent";
tooltip.innerHTML = `
<div class="tooltip-content">
<div class="fill-left"></div>
<div class="tip-box">
<div>
<div class="label" style="color: white; font-weight: bold;"></div>
<img class="close-icon" src="${getUrl(
"svg/Xwhite.svg"
)}" alt=""
style="cursor: pointer; " />
</div>
<img class="traffic-obfuscation-img" src="${getUrl(
"image/traffic-obfuscation.png"
)}" alt="" />
</div>
<img class="line-img-left" src="${getUrl(
"svg/anti-forensics-forwarding/LineLeft.svg"
)}" alt="" />
</div>
`;
// 添加到DOM
document.body.appendChild(tooltip);
customTooltip2Ref.current = tooltip;
// 添加关闭按钮事件
const closeButton = tooltip.querySelector(".close-icon");
if (closeButton) {
closeButton.addEventListener("click", () => {
setTooltipClosed(false);
tooltip.remove();
customTooltip2Ref.current = null;
});
}
// 定位提示框
positionCustomTooltip2();
};
// 定位自定义提示框 - 优化版本
const positionCustomTooltip2 = () => {
if (!customTooltip2Ref.current || !proxyGeoRef.current) return;
// 找到US点
const coords = geoCoordMap["ZA"];
if (!coords) return;
try {
// 将地理坐标转换为屏幕坐标
const screenCoord = proxyGeoRef.current.convertToPixel("geo", coords);
if (
screenCoord &&
Array.isArray(screenCoord) &&
screenCoord.length === 2
) {
// 设置提示框位置
customTooltip2Ref.current.style.left = `${
screenCoord[0] - 626 + 20
}px`;
customTooltipRef.current.style.top = `${
screenCoord[1] + 40 - 190
customTooltip2Ref.current.style.top = `${
screenCoord[1] + 40 - 218
}px`;
}
} catch (error) {
@ -195,13 +268,7 @@ export const WorldGeo = memo(
data
.map((item: any) => {
const countryCode = item.country_code.toUpperCase();
if (
!(
["RU", "FR"].includes(countryCode) &&
item.type === "start"
)
)
if (!(["RU", "FR"].includes(countryCode) && item.type === "start"))
return null;
const coords = geoCoordMap[countryCode] as
| [number, number]
@ -238,52 +305,70 @@ export const WorldGeo = memo(
const solidData: LinesType[] = [["main", []]]; // 使用"main"替代startCountry.country_code
// 收集需要显示涟漪效果的所有点(包括连线和不连线的)
const ripplePoints: any[] = [];
// 处理主路径数据
for (let i = 0; i < mainToData.length; i++) {
// 如果是最后一个元素,则跳过(因为没有下一个元素作为终点)
if (i === mainToData.length - 1) continue;
const currentItem = mainToData[i];
const nextItem = mainToData[i + 1];
// 获取当前国家代码
// 获取当前国家代码和颜色
const countryCode = currentItem.country_code.toUpperCase();
const color = currentItem.color || "#0ea5e9"; // 获取颜色,如果没有则使用默认颜色
// 如果当前项是起点,下一项是终点
if (currentItem.type === "start" && nextItem.type === "end") {
const startCode = countryCode;
const endCode = nextItem.country_code.toUpperCase();
// 无论是否连线,都添加点的涟漪效果
const startPoint = createCountryRipple(startCode);
const endPoint = createCountryRipple(endCode);
const startPoint = createCountryRipple(startCode, color);
const endPoint = createCountryRipple(
endCode,
nextItem.color || color
);
if (startPoint) ripplePoints.push(startPoint);
if (endPoint) ripplePoints.push(endPoint);
// 检查是否应该绘制连线
if (currentItem.isLine !== false) {
solidData[0]?.[1].push(getLineItem(startCode, endCode));
const lineItem = getLineItem(startCode, endCode);
// 添加颜色属性
lineItem[0].color = color;
lineItem[1].color = nextItem.color || color;
solidData[0]?.[1].push(lineItem);
}
// 跳过下一项,因为已经处理了
i++;
}
// 常规情况:当前项到下一项
else {
const nextCountryCode = nextItem.country_code.toUpperCase();
// 无论是否连线,都添加点的涟漪效果
const currentPoint = createCountryRipple(countryCode);
const nextPoint = createCountryRipple(nextCountryCode);
const currentPoint = createCountryRipple(countryCode, color);
const nextPoint = createCountryRipple(
nextCountryCode,
nextItem.color || color
);
if (currentPoint) ripplePoints.push(currentPoint);
if (nextPoint) ripplePoints.push(nextPoint);
// 检查是否应该绘制连线
if (currentItem.isLine !== false) {
solidData[0]?.[1].push(
getLineItem(countryCode, nextCountryCode)
);
const lineItem = getLineItem(countryCode, nextCountryCode);
// 添加颜色属性
lineItem[0].color = color;
lineItem[1].color = nextItem.color || color;
solidData[0]?.[1].push(lineItem);
}
}
}
// 虚线数据处理(保持原有逻辑)
const pathList =
screenData?.path_list?.filter(
(v: any) => v.name !== screenData?.proxy_info?.name
) ?? [];
const otherLineList = pathList.map(() => {});
const otherLineList: any = [];
return {
solidData,
otherLineList,
@ -294,33 +379,32 @@ export const WorldGeo = memo(
const convertData = (data: LinesDataType[]) => {
const res = [];
const midpoints = [];
for (let index = 0; index < data.length; index++) {
const dataIndex = data[index];
const fromCoord =
geoCoordMap[dataIndex?.[0]?.country_code ?? ""];
const fromCoord = geoCoordMap[dataIndex?.[0]?.country_code ?? ""];
const toCoord = geoCoordMap[dataIndex?.[1]?.country_code ?? ""];
const fromCountry = dataIndex?.[0]?.country_code ?? "";
const toCountry = dataIndex?.[1]?.country_code ?? "";
if (fromCoord && toCoord) {
res.push([fromCoord, toCoord]);
res.push({
coords: [fromCoord, toCoord],
// 添加颜色属性
lineStyle: {
color: dataIndex?.[0]?.color || "#0ea5e9",
},
});
// 计算中点,考虑曲线的弧度
const curveness = -0.4; // 与飞线弧度相同
const x1 = fromCoord[0];
const y1 = fromCoord[1];
const x2 = toCoord[0];
const y2 = toCoord[1];
// 计算控制点
const cpx = (x1 + x2) / 2 - (y2 - y1) * curveness;
const cpy = (y1 + y2) / 2 - (x1 - x2) * curveness;
// 计算曲线上的中点 (t=0.5 时的贝塞尔曲线点)
const midX = x1 * 0.25 + cpx * 0.5 + x2 * 0.25;
const midY = y1 * 0.25 + cpy * 0.5 + y2 * 0.25;
midpoints.push({
id: `line-label-${index}`,
midpoint: [midX, midY],
@ -329,16 +413,15 @@ export const WorldGeo = memo(
});
}
}
// 更新中点引用
lineMidpointsRef.current = midpoints;
// lineMidpointsRef.current = midpoints;
return res;
};
// 创建双层点效果 - 大点
const createDualLayerPoint = (
lastExit: LinesItemType,
isMainPath: boolean = true
isMainPath: boolean = true,
color?: string
) => {
// 创建数据数组,用于两个散点图层
const pointData = lastExit
@ -348,6 +431,7 @@ export const WorldGeo = memo(
value: v.value,
datas: {
country_code: v.country_code,
color: v.color, // 添加颜色属性
},
};
})
@ -355,8 +439,8 @@ export const WorldGeo = memo(
// 根据是否是主路径设置不同的大小和颜色
const outerSize = isMainPath ? 8 : 4;
const innerSize = isMainPath ? 4 : 2;
// Use selectedApp.color if available, otherwise default to blue
const outerColor = selectedApp?.color || "#0ea5e9"; // Use selectedApp.color with fallback
// 使用传入的颜色或从数据中获取颜色,如果都没有则使用默认颜色
const outerColor = color || lastExit?.color || "#0ea5e9";
const innerColor = "#FFFFFF"; // 白色内层
return [
{
@ -364,7 +448,9 @@ export const WorldGeo = memo(
type: "effectScatter",
coordinateSystem: "geo",
zlevel: 3,
itemStyle: {
color: outerColor,
},
symbol: "circle",
symbolSize: outerSize,
rippleEffect: {
@ -417,10 +503,42 @@ export const WorldGeo = memo(
} as echarts.SeriesOption,
];
};
// 添加新方法:根据经纬度数组创建蓝色涟漪小点(不包含白色内层点)
const createRipplePointsFromCoordinates = (
coordinates: [number, number][],
series: echarts.SeriesOption[]
) => {
if (!coordinates || coordinates.length === 0) return;
// 使用selectedApp.color或默认蓝色
const outerColor = "#01FF5E";
// 只创建外层带涟漪效果的点
series.push({
type: "effectScatter",
coordinateSystem: "geo",
zlevel: 3,
color: outerColor,
symbol: "circle",
symbolSize: 6,
rippleEffect: {
period: 8, // 动画时间
brushType: "stroke", // 波纹绘制方式
scale: 6, // 波纹圆环最大限制
brushWidth: 2,
},
label: {
show: false,
},
data: coordinates.map((coord) => ({
name: "", // 可以根据需要添加名称
value: coord,
})),
} as echarts.SeriesOption);
};
// 创建路径点的双层效果
const createPathPoints = (
dataItems: LinesDataType[],
isMainPath: boolean = true
isMainPath: boolean = true,
color?: string
) => {
// 创建数据数组
const pointData = dataItems.map((dataItem: LinesDataType) => {
@ -429,14 +547,15 @@ export const WorldGeo = memo(
value: geoCoordMap[dataItem[0].country_code],
datas: {
country_code: dataItem[0].country_code,
color: dataItem[0].color, // 添加颜色属性
},
};
});
// 根据是否是主路径设置不同的大小和颜色
const outerSize = isMainPath ? 8 : 4;
const innerSize = isMainPath ? 4 : 2;
// Use selectedApp.color if available, otherwise default to blue
const outerColor = selectedApp?.color || "#0ea5e9"; // Use selectedApp.color with fallback
// 使用传入的颜色或从数据中获取颜色,如果都没有则使用默认颜色
const outerColor = color || dataItems[0]?.[0]?.color || "#0ea5e9";
const innerColor = "#FFFFFF"; // 白色内层
return [
{
@ -444,7 +563,9 @@ export const WorldGeo = memo(
type: "effectScatter",
coordinateSystem: "geo",
zlevel: 3,
itemStyle: {
color: outerColor,
},
symbol: "circle",
symbolSize: outerSize,
rippleEffect: {
@ -497,13 +618,17 @@ export const WorldGeo = memo(
};
// 创建带自定义提示框的涟漪点
const createRipplePointsWithTooltip = (ripplePoints: any) => {
// Use selectedApp.color if available, otherwise default to blue
const outerColor = selectedApp?.color || "#0ea5e9"; // Use selectedApp.color with fallback
console.log(ripplePoints, "asdasdas");
return {
type: "effectScatter",
coordinateSystem: "geo",
zlevel: 3,
color: outerColor,
// 使用函数动态设置每个点的颜色
itemStyle: {
color: (params: any) => {
return params.data.datas?.color || "#0ea5e9"; // 使用点的颜色或默认颜色
},
},
symbol: "circle",
symbolSize: 8,
rippleEffect: {
@ -547,6 +672,7 @@ export const WorldGeo = memo(
value: point.value,
datas: {
country_code: point.country_code,
color: point.color, // 添加颜色属性
},
})),
} as echarts.SeriesOption;
@ -574,16 +700,21 @@ export const WorldGeo = memo(
value: point.value,
datas: {
country_code: point.country_code,
color: point.color, // 添加颜色属性
},
})),
} as echarts.SeriesOption);
}
solidData.forEach((item) => {
console.log(item, "item");
// 如果没有连线数据,则跳过
if (item[1].length === 0) {
return;
}
const lastExit = item[1]?.[item[1].length - 1]?.[1] ?? null;
// 获取当前路径的颜色
const pathColor = item[1]?.[0]?.[0]?.color || "#0ea5e9"; // 从第一个点获取颜色,如果没有则使用默认颜色
// 添加飞行线
series.push({
name: item[0],
@ -604,26 +735,29 @@ export const WorldGeo = memo(
lineStyle: {
curveness: -0.4, // 飞线弧度
type: "solid", // 飞线类型
color: selectedApp?.color || "#0ea5e9", // Use selectedApp.color with fallback
color: pathColor, // 使用从数据中获取的颜色
width: 1.5, // 飞线宽度
opacity: 0.1,
},
data: convertData(
item[1]
) as echarts.LinesSeriesOption["data"],
data: convertData(item[1]) as echarts.LinesSeriesOption["data"],
});
// 添加路径点的双层效果
const pathPoints = createPathPoints(item[1], true);
const pathPoints = createPathPoints(item[1], true, pathColor);
series.push(...pathPoints);
// 添加出口节点的双层效果
if (lastExit) {
const exitNodes = createDualLayerPoint(lastExit, true);
const exitNodes = createDualLayerPoint(lastExit, true, pathColor);
series.push(...exitNodes);
}
});
otherLineList.forEach((line: any) => {
line.forEach((item: any) => {
const lastExit = item[1]?.[item[1].length - 1]?.[1] ?? null;
// 获取当前路径的颜色
const pathColor = item[1]?.[0]?.[0]?.color || "#F0FFA2"; // 从第一个点获取颜色,如果没有则使用默认颜色
// 添加虚线
series.push({
name: item[0],
@ -636,20 +770,18 @@ export const WorldGeo = memo(
lineStyle: {
curveness: -0.4, // 飞线弧度
type: [5, 5], // 飞线类型
color: "#F0FFA2", // 飞线颜色
color: pathColor, // 使用从数据中获取的颜色
width: 0.5, // 飞线宽度
opacity: 0.6,
},
data: convertData(
item[1]
) as echarts.LinesSeriesOption["data"],
data: convertData(item[1]) as echarts.LinesSeriesOption["data"],
});
// 添加路径点的双层效果(次要路径)
const pathPoints = createPathPoints(item[1], false);
const pathPoints = createPathPoints(item[1], false, pathColor);
series.push(...pathPoints);
// 添加出口节点的双层效果(次要路径)
if (lastExit) {
const exitNodes = createDualLayerPoint(lastExit, false);
const exitNodes = createDualLayerPoint(lastExit, false, pathColor);
series.push(...exitNodes);
}
});
@ -700,7 +832,6 @@ export const WorldGeo = memo(
height: 35,
width: 8,
},
gap2: {
height: 35,
width: 6,
@ -744,14 +875,12 @@ export const WorldGeo = memo(
}
);
};
// 创建A点和B点并添加飞线和标签
const createSpecialPoints = (series: echarts.SeriesOption[]) => {
// 定义点A和点B的坐标
const pointA = [-42.604303, 71.706936];
const pointB = [-106.346771, 56.130366];
const newPointB = [pointB[0] + 14, pointB[1] + 10];
// 添加A点 - 带涟漪效果的双层点
series.push(
// 外层带涟漪效果的点
@ -794,7 +923,6 @@ export const WorldGeo = memo(
},
],
} as echarts.SeriesOption,
// 内层白色点
{
type: "scatter", // 普通scatter不带特效
@ -814,7 +942,6 @@ export const WorldGeo = memo(
],
} as echarts.SeriesOption
);
// 添加B点 - 大型圆形区域
series.push({
type: "scatter",
@ -849,7 +976,6 @@ export const WorldGeo = memo(
},
],
} as echarts.SeriesOption);
// 添加A到B的飞线无特效
series.push({
type: "lines",
@ -870,22 +996,18 @@ export const WorldGeo = memo(
},
],
} as echarts.SeriesOption);
// 计算飞线中点坐标(考虑曲率)
const x1 = pointA[0];
const y1 = pointA[1];
const x2 = newPointB[0];
const y2 = newPointB[1];
const curveness = -0.4;
// 计算控制点
const cpx = (x1 + x2) / 2 - (y2 - y1) * curveness;
const cpy = (y1 + y2) / 2 - (x1 - x2) * curveness;
// 计算曲线上的中点 (t=0.5 时的贝塞尔曲线点)
const midX = x1 * 0.25 + cpx * 0.5 + x2 * 0.25;
const midY = y1 * 0.25 + cpy * 0.5 + y2 * 0.25;
// 将中点添加到 lineMidpointsRef 中,以便使用 DOM 方式创建标签
lineMidpointsRef.current.push({
id: "special-line-label",
@ -893,15 +1015,21 @@ export const WorldGeo = memo(
fromCountry: "A",
toCountry: "B",
});
return series;
};
const getOption = () => {
const series: echarts.SeriesOption[] = [];
getLianData(series);
// getMianLineTipData(series); // 添加主线tip 暂时隐藏
if (tooltipType === "PASS_AUTHENTICATION") {
createSpecialPoints(series); // 添加特殊点和飞线
if (newHomeProxies[0]?.authenticationPoint) {
createRipplePointsFromCoordinates(
newHomeProxies[0]?.authenticationPoint || [],
series
);
}
}
const option = {
backgroundColor: "transparent",
@ -969,8 +1097,7 @@ export const WorldGeo = memo(
}
| undefined;
}) => {
if (parameters.data?.name)
return parameters.data.name;
if (parameters.data?.name) return parameters.data.name;
return parameters.name;
},
},
@ -979,12 +1106,12 @@ export const WorldGeo = memo(
};
return option;
};
// 创建DOM标签
const createDOMLabels = () => {
// 清除现有标签
if (labelContainerRef.current) {
labelContainerRef.current.innerHTML = "";
labelsRef.current?.forEach((item) => item?.remove());
labelsRef.current = [];
} else {
// 创建标签容器
@ -998,7 +1125,6 @@ export const WorldGeo = memo(
container.style.width = "100%";
container.style.height = "100%";
container.style.overflow = "hidden";
// 添加到地图容器
const chartDom = document.getElementById("screenGeo");
if (chartDom) {
@ -1007,7 +1133,6 @@ export const WorldGeo = memo(
labelContainerRef.current = container;
}
}
// 创建新标签
lineMidpointsRef.current.forEach((point, index) => {
const label = document.createElement("div");
@ -1019,7 +1144,6 @@ export const WorldGeo = memo(
label.style.whiteSpace = "nowrap";
label.style.pointerEvents = "none";
label.style.zIndex = "1001";
// 特殊线标签A到B的线
if (point.id === "special-line-label") {
label.style.backgroundColor = "#8B3700";
@ -1040,24 +1164,19 @@ export const WorldGeo = memo(
label.style.fontWeight = "normal";
label.textContent = "SS签名";
}
// 添加到容器
labelContainerRef.current?.appendChild(label);
labelsRef.current.push(label);
});
// 更新标签位置
updateLabelPositions();
};
// 更新标签位置
const updateLabelPositions = () => {
if (!proxyGeoRef.current || !labelContainerRef.current) return;
lineMidpointsRef.current.forEach((point, index) => {
const label = labelsRef.current[index];
if (!label) return;
const pixelPoint = proxyGeoRef.current?.convertToPixel(
"geo",
point.midpoint
@ -1068,64 +1187,70 @@ export const WorldGeo = memo(
}
});
};
const handleResize = () => {
proxyGeoRef.current?.resize();
updateLabelPositions();
};
useEffect(() => {
preMainToData.current?.some(
(item, index) =>
item.country_code !== mainToData[index]?.country_code
(item, index) => item.country_code !== mainToData[index]?.country_code
) && proxyGeoRef.current?.clear();
preMainToData.current = mainToData;
const option = getOption();
proxyGeoRef.current?.setOption(option);
// 创建DOM标签
setTimeout(createDOMLabels, 100);
}, [screenData, mainToData]);
}, [newHomeProxies, mainToData]);
useEffect(() => {
const chartDom = document.getElementById("screenGeo");
proxyGeoRef.current = echarts.init(chartDom);
echarts.registerMap(
"world",
worldGeoJson as unknown as Parameters<
typeof echarts.registerMap
>[1]
worldGeoJson as unknown as Parameters<typeof echarts.registerMap>[1]
);
const option = getOption();
option && proxyGeoRef.current?.setOption(option);
// 添加地图交互事件监听器
proxyGeoRef.current?.on("georoam", updateLabelPositions);
// 页面resize时触发
window.addEventListener("resize", handleResize);
return () => {
window.removeEventListener("resize", handleResize);
proxyGeoRef.current?.off("georoam", updateLabelPositions);
// 清理DOM标签
if (labelContainerRef.current) {
labelContainerRef.current.remove();
labelContainerRef.current = null;
labelsRef.current = [];
}
proxyGeoRef.current?.dispose();
proxyGeoRef.current = null;
};
}, []);
useEffect(() => {
if (tooltipType !== "PASS_AUTHENTICATION") {
lineMidpointsRef.current = [];
}
if (tooltipClosed) {
if (tooltipType === "NESTED_ENCRYPTION") {
createCustomTooltip();
}
if (tooltipType === "TRAFFIC_OBFUSCATION") {
createCustomTooltip2();
}
} else {
customTooltipRef.current?.remove();
customTooltip2Ref.current?.remove();
customTooltipRef.current = null;
customTooltip2Ref.current = null;
}
return () => {
customTooltipRef.current?.remove();
customTooltip2Ref.current?.remove();
customTooltipRef.current = null;
customTooltip2Ref.current = null;
};
}, [tooltipClosed, tooltipType]);
return (
<div className="flex-1 h-full flex flex-col">
<div id="screenGeo" className="flex-1"></div>

View File

@ -0,0 +1,447 @@
export const TRAFFIC_OBFUSCATION = {
type: "NESTED_ENCRYPTION",
name: "流量混淆",
code: "BR",
data: [
{
country_code: "gl",
ingress_country_code: "za",
},
{
country_code: "za",
ingress_country_code: "dz",
},
{
country_code: "dz",
ingress_country_code: "ru",
},
{
country_code: "dz",
ingress_country_code: "cn",
},
],
isLine: true,
};
export const NESTED_ENCRYPTION = {
type: "NESTED_ENCRYPTION",
name: "嵌套加密",
code: "GL",
data: [
{
country_code: "gl",
ingress_country_code: "br",
},
{
country_code: "br",
ingress_country_code: "dz",
},
{
country_code: "dz",
ingress_country_code: "ru",
},
{
country_code: "dz",
ingress_country_code: "cn",
},
],
isLine: true,
};
export const DYNAMIC_ROUTE_GENERATOR = [
{
type: "DYNAMIC_ROUTE_GENERATOR",
name: "动态路由生成",
data: [
{
country_code: "us",
ingress_country_code: "ca",
},
{
country_code: "ca",
ingress_country_code: "gl",
},
{
country_code: "gl",
ingress_country_code: "by",
},
{
country_code: "dz",
ingress_country_code: "cn",
},
],
color: "#48D3D5",
isLine: true,
},
{
type: "DYNAMIC_ROUTE_GENERATOR",
name: "动态路由生成2",
data: [
{
country_code: "br",
ingress_country_code: "ml",
},
{
country_code: "ml",
ingress_country_code: "ly",
},
{
country_code: "ly",
ingress_country_code: "cn",
},
{
country_code: "cn",
ingress_country_code: "ru",
},
],
color: "#50FE35",
isLine: true,
},
];
export const APP_DIVERSION = [
{
name: "Netflix",
color: "#DC2626",
data: [
{
country_code: "mg",
ingress_country_code: "ru",
},
{
country_code: "ru",
ingress_country_code: "fr",
},
{
country_code: "fr",
ingress_country_code: "br",
},
{
country_code: "br",
ingress_country_code: "us",
},
],
isLine: true,
},
{
name: "Spotify",
color: "#22C55E",
data: [
{
country_code: "jp",
ingress_country_code: "au",
},
{
country_code: "au",
ingress_country_code: "za",
},
{
country_code: "za",
ingress_country_code: "de",
},
{
country_code: "de",
ingress_country_code: "ca",
},
],
isLine: true,
},
{
name: "Instagram",
color: "#8B5CF6",
data: [
{
country_code: "it",
ingress_country_code: "in",
},
{
country_code: "in",
ingress_country_code: "mx",
},
{
country_code: "mx",
ingress_country_code: "se",
},
{
country_code: "se",
ingress_country_code: "sg",
},
],
isLine: true,
},
{
name: "Telegram",
color: "#2563EB",
data: [
{
country_code: "ar",
ingress_country_code: "nl",
},
{
country_code: "nl",
ingress_country_code: "kr",
},
{
country_code: "kr",
ingress_country_code: "eg",
},
{
country_code: "eg",
ingress_country_code: "nz",
},
],
isLine: true,
},
{
name: "Google",
color: "#3B82F6",
data: [
{
country_code: "ch",
ingress_country_code: "br",
},
{
country_code: "br",
ingress_country_code: "hk",
},
{
country_code: "hk",
ingress_country_code: "no",
},
{
country_code: "no",
ingress_country_code: "ae",
},
],
isLine: true,
},
{
name: "Gmail",
color: "#22C55E",
data: [
{
country_code: "es",
ingress_country_code: "cn",
},
{
country_code: "cn",
ingress_country_code: "co",
},
{
country_code: "co",
ingress_country_code: "fi",
},
{
country_code: "fi",
ingress_country_code: "id",
},
],
isLine: true,
},
{
name: "Amazon",
color: "#EAB308",
data: [
{
country_code: "gb",
ingress_country_code: "th",
},
{
country_code: "th",
ingress_country_code: "cl",
},
{
country_code: "cl",
ingress_country_code: "be",
},
{
country_code: "be",
ingress_country_code: "ph",
},
],
isLine: true,
},
{
name: "Ebay",
color: "#3B82F6",
data: [
{
country_code: "pl",
ingress_country_code: "my",
},
{
country_code: "my",
ingress_country_code: "pe",
},
{
country_code: "pe",
ingress_country_code: "dk",
},
{
country_code: "dk",
ingress_country_code: "ng",
},
],
isLine: true,
},
{
name: "AppleNews",
color: "#EF4444",
data: [
{
country_code: "ie",
ingress_country_code: "vn",
},
{
country_code: "vn",
ingress_country_code: "ma",
},
{
country_code: "ma",
ingress_country_code: "at",
},
{
country_code: "at",
ingress_country_code: "tw",
},
],
isLine: true,
},
{
name: "CNN",
color: "#EF4444",
data: [
{
country_code: "ua",
ingress_country_code: "sa",
},
{
country_code: "sa",
ingress_country_code: "gr",
},
{
country_code: "gr",
ingress_country_code: "pk",
},
{
country_code: "pk",
ingress_country_code: "pt",
},
],
isLine: true,
},
{
name: "Browser",
color: "#8B5CF6",
data: [
{
country_code: "il",
ingress_country_code: "ro",
},
{
country_code: "ro",
ingress_country_code: "nz",
},
{
country_code: "nz",
ingress_country_code: "tr",
},
{
country_code: "tr",
ingress_country_code: "ca",
},
],
isLine: true,
},
{
name: "YouTube",
color: "#EF4444",
data: [
{
country_code: "cz",
ingress_country_code: "sg",
},
{
country_code: "sg",
ingress_country_code: "ar",
},
{
country_code: "ar",
ingress_country_code: "hu",
},
{
country_code: "hu",
ingress_country_code: "jp",
},
],
isLine: true,
},
{
name: "Facebook",
color: "#3B82F6",
data: [
{
country_code: "is",
ingress_country_code: "za",
},
{
country_code: "za",
ingress_country_code: "mx",
},
{
country_code: "mx",
ingress_country_code: "it",
},
{
country_code: "it",
ingress_country_code: "kr",
},
],
isLine: true,
},
];
export const PASS_AUTHENTICATION = {
type: "PASS_AUTHENTICATION",
name: "通行认证",
authenticationPoint: [
[-103.346771, 54.130366],
[-120.346771, 52.130366],
[-108.346771, 48.130366],
[-98.346771, 46.130366],
[-106.346771, 48.450366],
[-101.346771, 53.130366],
[-123.346771, 58.130366],
[-111.346771, 65.443366],
[-108.346771, 54.130366],
[-116.346771, 59.130366],
[-97.346771, 61.130366],
[-95.346771, 63.130366],
[-113.346771, 58.840366],
[-99.346771, 59.130366],
[-102.346771, 68.130366],
],
data: [
{
country_code: "gl",
ingress_country_code: "dz",
},
{
country_code: "br",
ingress_country_code: "dz",
},
{
country_code: "dz",
ingress_country_code: "ru",
},
{
country_code: "dz",
ingress_country_code: "cn",
},
{
country_code: "ru",
ingress_country_code: "za",
},
],
isLine: true,
};

View File

@ -131,6 +131,11 @@
width: 312.221px;
}
.line-img-left{
width: 216.86px;
margin-top: 30px;
}
.fill {
width: 9.165px;
height: 9.165px;
@ -141,6 +146,17 @@
top: 77.5px;
z-index: 99;
}
.fill-left {
width: 9.165px;
height: 9.165px;
border-radius: 50%;
background-color: #18E4FF;
position: absolute;
right:210.5px;
top: 82.5px;
z-index: 99;
}
}

View File

@ -30,8 +30,16 @@ import FacebookSvg from "@/assets/svg/anti-forensics-forwarding/Facebook.svg?rea
import FacebookActiveSvg from "@/assets/svg/anti-forensics-forwarding/FacebookActive.svg?react";
import { RootState } from "@/store";
import {
TRAFFIC_OBFUSCATION,
NESTED_ENCRYPTION,
DYNAMIC_ROUTE_GENERATOR,
APP_DIVERSION,
PASS_AUTHENTICATION,
} from "./data/mockData";
import "./index.scss";
import { color } from "echarts";
import { App } from "antd";
export const DIALOGTYPE = {
ADDNode: {
@ -64,365 +72,66 @@ export const Apps = [
name: "Netflix",
icon: NetflixSvg,
activeIcon: NetflixActiveSvg,
color: "#DC2626",
jumpList: {
data: [
{
country_code: "mg",
ingress_country_code: "ru",
},
{
country_code: "ru",
ingress_country_code: "fr",
},
{
country_code: "fr",
ingress_country_code: "br",
},
{
country_code: "br",
ingress_country_code: "us",
},
],
isLine: true,
name: "Netflix",
},
},
{
name: "Spotify",
icon: SpotifySvg,
activeIcon: SpotifyActiveSvg,
color: "#22C55E",
jumpList: {
data: [
{
country_code: "mg",
ingress_country_code: "ru",
},
{
country_code: "ru",
ingress_country_code: "fr",
},
{
country_code: "fr",
ingress_country_code: "br",
},
{
country_code: "br",
ingress_country_code: "us",
},
],
isLine: true,
name: "Spotify",
},
},
{
name: "Instagram",
icon: InstagramSvg,
activeIcon: InstagramActiveSvg,
color: "#8B5CF6",
jumpList: {
data: [
{
country_code: "mg",
ingress_country_code: "ru",
},
{
country_code: "ru",
ingress_country_code: "fr",
},
{
country_code: "fr",
ingress_country_code: "br",
},
{
country_code: "br",
ingress_country_code: "us",
},
],
isLine: true,
name: "Instagram",
},
},
{
name: "Telegram",
icon: TelegramSvg,
activeIcon: TelegramActiveSvg,
color: "#2563EB",
jumpList: {
data: [
{
country_code: "mg",
ingress_country_code: "ru",
},
{
country_code: "ru",
ingress_country_code: "fr",
},
{
country_code: "fr",
ingress_country_code: "br",
},
{
country_code: "br",
ingress_country_code: "us",
},
],
isLine: true,
name: "Telegram",
},
},
{
name: "Google",
icon: GoogleSvg,
activeIcon: GoogleActiveSvg,
color: "#3B82F6",
jumpList: {
data: [
{
country_code: "mg",
ingress_country_code: "ru",
},
{
country_code: "ru",
ingress_country_code: "fr",
},
{
country_code: "fr",
ingress_country_code: "br",
},
{
country_code: "br",
ingress_country_code: "us",
},
],
isLine: true,
name: "Google",
},
},
{
name: "Gmail",
icon: GmailSvg,
activeIcon: GmailActiveSvg,
color: "#22C55E",
jumpList: {
data: [
{
country_code: "mg",
ingress_country_code: "ru",
},
{
country_code: "ru",
ingress_country_code: "fr",
},
{
country_code: "fr",
ingress_country_code: "br",
},
{
country_code: "br",
ingress_country_code: "us",
},
],
isLine: true,
name: "Gmail",
},
},
{
name: "Amazon",
icon: AmazonSvg,
activeIcon: AmazonActiveSvg,
color: "#EAB308",
jumpList: {
data: [
{
country_code: "mg",
ingress_country_code: "ru",
},
{
country_code: "ru",
ingress_country_code: "fr",
},
{
country_code: "fr",
ingress_country_code: "br",
},
{
country_code: "br",
ingress_country_code: "us",
},
],
isLine: true,
name: "Amazon",
},
},
{
name: "Ebay",
icon: EbaySvg,
activeIcon: EbayActiveSvg,
color: "#3B82F6",
jumpList: {
data: [
{
country_code: "mg",
ingress_country_code: "ru",
},
{
country_code: "ru",
ingress_country_code: "fr",
},
{
country_code: "fr",
ingress_country_code: "br",
},
{
country_code: "br",
ingress_country_code: "us",
},
],
isLine: true,
name: "Ebay",
},
},
{
name: "AppleNews",
icon: AppleNewsSvg,
activeIcon: AppleNewsActiveSvg,
color: "#EF4444",
jumpList: {
data: [
{
country_code: "mg",
ingress_country_code: "ru",
},
{
country_code: "ru",
ingress_country_code: "fr",
},
{
country_code: "fr",
ingress_country_code: "br",
},
{
country_code: "br",
ingress_country_code: "us",
},
],
isLine: true,
name: "AppleNews",
},
},
{
name: "CNN",
icon: CNNSvg,
activeIcon: CNNActiveSvg,
color: "#EF4444",
jumpList: {
data: [
{
country_code: "mg",
ingress_country_code: "ru",
},
{
country_code: "ru",
ingress_country_code: "fr",
},
{
country_code: "fr",
ingress_country_code: "br",
},
{
country_code: "br",
ingress_country_code: "us",
},
],
isLine: true,
name: "CNN",
},
},
{
name: "Browser",
icon: BrowserSvg,
activeIcon: BrowserActiveSvg,
color: "#8B5CF6",
jumpList: {
data: [
{
country_code: "mg",
ingress_country_code: "ru",
},
{
country_code: "ru",
ingress_country_code: "fr",
},
{
country_code: "fr",
ingress_country_code: "br",
},
{
country_code: "br",
ingress_country_code: "us",
},
],
isLine: true,
name: "Browser",
},
},
{
name: "YouTube",
icon: YouTubeSvg,
activeIcon: YouTubeActiveSvg,
color: "#EF4444",
jumpList: {
data: [
{
country_code: "mg",
ingress_country_code: "ru",
},
{
country_code: "ru",
ingress_country_code: "fr",
},
{
country_code: "fr",
ingress_country_code: "br",
},
{
country_code: "br",
ingress_country_code: "us",
},
],
isLine: true,
name: "YouTube",
},
},
{
name: "Facebook",
icon: FacebookSvg,
activeIcon: FacebookActiveSvg,
color: "#3B82F6",
jumpList: {
data: [
{
country_code: "mg",
ingress_country_code: "ru",
},
{
country_code: "ru",
ingress_country_code: "fr",
},
{
country_code: "fr",
ingress_country_code: "br",
},
{
country_code: "br",
ingress_country_code: "us",
},
],
isLine: true,
name: "Facebook",
},
},
];
@ -435,36 +144,84 @@ export const CONST_TOOLTIP_TYPE = {
type: "TRAFFIC_OBFUSCATION",
title: "流量混淆",
},
DYNAMIC_ROUTE_GENERATOR: {
type: "DYNAMIC_ROUTE_GENERATOR",
title: "动态路由生成",
},
// 应用分流
APP_DIVERSION: {
type: "APP_DIVERSION",
title: "应用分流",
},
// 通行认证
PASS_AUTHENTICATION: {
type: "PASS_AUTHENTICATION",
title: "通行认证",
},
};
const DecentralizedElasticNetwork = () => {
const { proxy_info, path_list } = useSelector(
const { proxy_info, path_list, newHomeProxies } = useSelector(
(state: RootState) => state.web3Reducer
);
const [tooltipType, setTooltipType] = useState(
CONST_TOOLTIP_TYPE.NESTED_ENCRYPTION.type
CONST_TOOLTIP_TYPE.PASS_AUTHENTICATION.type
);
const [tooltipClosed, setTooltipClosed] = useState(false);
const [selectedApp, setSelectedApp] = useState<any>(null);
const appDiversion = useMemo(() => {
return Apps.map((item) => {
const findApp = APP_DIVERSION.find(
(appItem) => item.name === appItem.name
);
return {
...item,
...findApp,
};
});
}, []);
const currentValue = useMemo(() => {
let value = null;
switch (tooltipType) {
case CONST_TOOLTIP_TYPE.NESTED_ENCRYPTION.type:
value = [NESTED_ENCRYPTION];
break;
case CONST_TOOLTIP_TYPE.TRAFFIC_OBFUSCATION.type:
value = [TRAFFIC_OBFUSCATION];
break;
case CONST_TOOLTIP_TYPE.DYNAMIC_ROUTE_GENERATOR.type:
value = DYNAMIC_ROUTE_GENERATOR
break;
case CONST_TOOLTIP_TYPE.APP_DIVERSION.type:
value = selectedApp ? [selectedApp] : []
break;
case CONST_TOOLTIP_TYPE.PASS_AUTHENTICATION.type:
value = [PASS_AUTHENTICATION]
break;
default:
break;
}
return value;
}, [tooltipType,selectedApp]);
const handleClickApp = (item: any) => {
console.log("item", item);
setSelectedApp(item);
};
const screenData = useMemo(() => {
return {
path_list,
proxy_info,
};
}, [path_list, proxy_info]);
useEffect(()=>{
()=>{
setTooltipClosed(false);
}
},[])
return (
<div className="decentralized w-full h-full flex flex-col relative">
<div className="flex items-center gap-[60px] absolute top-12 left-12 z-10">
{Apps.map((item) => {
{tooltipType === CONST_TOOLTIP_TYPE.APP_DIVERSION.type && appDiversion.map((item) => {
return (
<div
key={item.name}
@ -482,7 +239,8 @@ const DecentralizedElasticNetwork = () => {
</div>
<div className="mt-2 w-full h-full flex-1">
<WorldGeo
screenData={screenData}
currentValue={currentValue}
newHomeProxies={newHomeProxies}
selectedApp={selectedApp}
tooltipType={tooltipType}
tooltipClosed={tooltipClosed}
@ -490,36 +248,47 @@ const DecentralizedElasticNetwork = () => {
/>
</div>
<div className="absolute bottom-6 left-[50%] translate-x-[-50%] w-[calc(100%-51px)] p-6 bg-indigo-950 bg-opacity-10 rounded-md outline outline-1 outline-zinc-200 outline-opacity-40 backdrop-blur-lg inline-flex justify-start items-center gap-4">
<div className="bt1 cursor-pointer" onClick={() => {}}>
<div
className="bt1 cursor-pointer"
onClick={() => {
setTooltipType(CONST_TOOLTIP_TYPE.PASS_AUTHENTICATION.type);
}}
>
</div>
<div
className="bt1 cursor-pointer"
onClick={() => {
setTooltipType(
CONST_TOOLTIP_TYPE.NESTED_ENCRYPTION.type
);
setTooltipType(CONST_TOOLTIP_TYPE.NESTED_ENCRYPTION.type);
setTooltipClosed(true);
}}
>
</div>
<div className="bt1 cursor-pointer" onClick={() => {}}>
<div
className="bt1 cursor-pointer"
onClick={() => {
setTooltipType(CONST_TOOLTIP_TYPE.DYNAMIC_ROUTE_GENERATOR.type);
}}
>
</div>
<div
className="bt1 cursor-pointer"
onClick={() => {
setTooltipType(
CONST_TOOLTIP_TYPE.TRAFFIC_OBFUSCATION.type
);
setTooltipType(CONST_TOOLTIP_TYPE.TRAFFIC_OBFUSCATION.type);
setTooltipClosed(true);
}}
>
</div>
<div className="bt1 cursor-pointer" onClick={() => {}}>
<div
className="bt1 cursor-pointer"
onClick={() => {
setTooltipType(CONST_TOOLTIP_TYPE.APP_DIVERSION.type);
}}
>
</div>
</div>

View File

@ -57,13 +57,10 @@ export const WorldGeo = memo(
>([]);
const labelContainerRef = useRef<HTMLDivElement | null>(null);
const labelsRef = useRef<HTMLDivElement[]>([]);
const mainToData = useMemo(() => {
// 使用新的数据结构
const proxiesList =
selectedApp && selectedApp?.jumpList
? [selectedApp.jumpList]
: newHomeProxies ?? [];
selectedApp && selectedApp ? [...newHomeProxies,selectedApp] : newHomeProxies ?? [];
// 初始化数据数组 - 不再包含 startCountry
const data: any = [];
// 遍历代理列表
@ -79,18 +76,21 @@ export const WorldGeo = memo(
country_code: item.country_code,
type: "start",
isLine: proxyItem.isLine, // 保存连线标志
color: proxyItem.color, // 保存颜色信息
});
// 添加终点ingress_country_code
data.push({
country_code: item.ingress_country_code,
type: "end",
isLine: proxyItem.isLine, // 保存连线标志
color: proxyItem.color, // 保存颜色信息
});
} else {
// 如果没有 ingress_country_code只添加 country_code
data.push({
country_code: item.country_code,
isLine: proxyItem.isLine, // 保存连线标志
color: proxyItem.color, // 保存颜色信息
});
}
});
@ -98,7 +98,6 @@ export const WorldGeo = memo(
});
return data;
}, [newHomeProxies, selectedApp]);
// 创建自定义提示框DOM元素
const createCustomTooltip = () => {
// 如果已经存在自定义提示框,则移除它
@ -114,9 +113,8 @@ export const WorldGeo = memo(
tooltip.style.backgroundColor = "transparent";
// 设置提示框内容
const currentTooltipType =
CONST_TOOLTIP_TYPE[
tooltipType as keyof typeof CONST_TOOLTIP_TYPE
] || CONST_TOOLTIP_TYPE.NESTED_ENCRYPTION;
CONST_TOOLTIP_TYPE[tooltipType as keyof typeof CONST_TOOLTIP_TYPE] ||
CONST_TOOLTIP_TYPE.NESTED_ENCRYPTION;
tooltip.innerHTML = `
<div class="tooltip-content">
<img class="line-img" src="${getUrl(
@ -133,19 +131,16 @@ export const WorldGeo = memo(
)}" alt=""
style="cursor: pointer; " />
</div>
<img class="${
CONST_TOOLTIP_TYPE.NESTED_ENCRYPTION.type ===
currentTooltipType.type
? "encryption-img"
: "traffic-obfuscation-img"
}" src="${getUrl(
CONST_TOOLTIP_TYPE.NESTED_ENCRYPTION.type ===
currentTooltipType.type
CONST_TOOLTIP_TYPE.NESTED_ENCRYPTION.type === currentTooltipType.type
? "image/nested-encryption.png"
: "image/traffic-obfuscation.png"
)}" alt="" />
</div>
</div>
`;
@ -172,21 +167,88 @@ export const WorldGeo = memo(
if (!coords) return;
try {
// 将地理坐标转换为屏幕坐标
const screenCoord = proxyGeoRef.current.convertToPixel(
"geo",
coords
);
const screenCoord = proxyGeoRef.current.convertToPixel("geo", coords);
if (
screenCoord &&
Array.isArray(screenCoord) &&
screenCoord.length === 2
) {
// 设置提示框位置
customTooltipRef.current.style.left = `${
screenCoord[0] + 232 + 7
customTooltipRef.current.style.left = `${screenCoord[0] + 232 + 7}px`;
customTooltipRef.current.style.top = `${screenCoord[1] + 40 - 190}px`;
}
} catch (error) {
console.error("Error positioning tooltip:", error);
}
};
// 创建自定义提示框DOM元素
const createCustomTooltip2 = () => {
// 如果已经存在自定义提示框,则移除它
if (document.getElementById("custom-fixed-tooltip2")) {
document.getElementById("custom-fixed-tooltip2")?.remove();
}
// 创建自定义提示框
const tooltip = document.createElement("div");
tooltip.id = "custom-fixed-tooltip2";
tooltip.style.position = "fixed";
tooltip.style.zIndex = "1000";
tooltip.style.pointerEvents = "auto";
tooltip.style.backgroundColor = "transparent";
tooltip.innerHTML = `
<div class="tooltip-content">
<div class="fill-left"></div>
<div class="tip-box">
<div>
<div class="label" style="color: white; font-weight: bold;"></div>
<img class="close-icon" src="${getUrl(
"svg/Xwhite.svg"
)}" alt=""
style="cursor: pointer; " />
</div>
<img class="traffic-obfuscation-img" src="${getUrl(
"image/traffic-obfuscation.png"
)}" alt="" />
</div>
<img class="line-img-left" src="${getUrl(
"svg/anti-forensics-forwarding/LineLeft.svg"
)}" alt="" />
</div>
`;
// 添加到DOM
document.body.appendChild(tooltip);
customTooltip2Ref.current = tooltip;
// 添加关闭按钮事件
const closeButton = tooltip.querySelector(".close-icon");
if (closeButton) {
closeButton.addEventListener("click", () => {
setTooltipClosed(false);
tooltip.remove();
customTooltip2Ref.current = null;
});
}
// 定位提示框
positionCustomTooltip2();
};
// 定位自定义提示框 - 优化版本
const positionCustomTooltip2 = () => {
if (!customTooltip2Ref.current || !proxyGeoRef.current) return;
// 找到US点
const coords = geoCoordMap["ZA"];
if (!coords) return;
try {
// 将地理坐标转换为屏幕坐标
const screenCoord = proxyGeoRef.current.convertToPixel("geo", coords);
if (
screenCoord &&
Array.isArray(screenCoord) &&
screenCoord.length === 2
) {
// 设置提示框位置
customTooltip2Ref.current.style.left = `${
screenCoord[0] - 626 + 20
}px`;
customTooltipRef.current.style.top = `${
screenCoord[1] + 40 - 190
customTooltip2Ref.current.style.top = `${
screenCoord[1] + 40 - 218
}px`;
}
} catch (error) {
@ -199,13 +261,7 @@ export const WorldGeo = memo(
data
.map((item: any) => {
const countryCode = item.country_code.toUpperCase();
if (
!(
["RU", "FR"].includes(countryCode) &&
item.type === "start"
)
)
if (!(["RU", "FR"].includes(countryCode) && item.type === "start"))
return null;
const coords = geoCoordMap[countryCode] as
| [number, number]
@ -242,6 +298,9 @@ export const WorldGeo = memo(
const solidData: LinesType[] = [["main", []]]; // 使用"main"替代startCountry.country_code
// 收集需要显示涟漪效果的所有点(包括连线和不连线的)
const ripplePoints: any[] = [];
// 收集每个点的颜色信息
const pointColors: Record<string, string> = {};
// 处理主路径数据
for (let i = 0; i < mainToData.length; i++) {
// 如果是最后一个元素,则跳过(因为没有下一个元素作为终点)
@ -250,18 +309,46 @@ export const WorldGeo = memo(
const nextItem = mainToData[i + 1];
// 获取当前国家代码
const countryCode = currentItem.country_code.toUpperCase();
// 获取颜色信息
const lineColor = currentItem.color || "#0ea5e9"; // 默认颜色
// 保存点的颜色信息
pointColors[countryCode] = lineColor;
// 如果当前项是起点,下一项是终点
if (currentItem.type === "start" && nextItem.type === "end") {
const startCode = countryCode;
const endCode = nextItem.country_code.toUpperCase();
// 保存终点的颜色信息
pointColors[endCode] = lineColor;
// 无论是否连线,都添加点的涟漪效果
const startPoint = createCountryRipple(startCode);
const endPoint = createCountryRipple(endCode);
if (startPoint) ripplePoints.push(startPoint);
if (endPoint) ripplePoints.push(endPoint);
if (startPoint) {
ripplePoints.push({
...startPoint,
color: lineColor // 添加颜色信息
});
}
if (endPoint) {
ripplePoints.push({
...endPoint,
color: lineColor // 添加颜色信息
});
}
// 检查是否应该绘制连线
if (currentItem.isLine !== false) {
solidData[0]?.[1].push(getLineItem(startCode, endCode));
const lineItem = getLineItem(startCode, endCode);
// 添加颜色信息到连线数据
solidData[0][1].push({
...lineItem,
color: lineColor // 添加颜色信息
} as any);
}
// 跳过下一项,因为已经处理了
i++;
@ -269,16 +356,36 @@ export const WorldGeo = memo(
// 常规情况:当前项到下一项
else {
const nextCountryCode = nextItem.country_code.toUpperCase();
// 保存下一个点的颜色信息
pointColors[nextCountryCode] = nextItem.color || lineColor;
// 无论是否连线,都添加点的涟漪效果
const currentPoint = createCountryRipple(countryCode);
const nextPoint = createCountryRipple(nextCountryCode);
if (currentPoint) ripplePoints.push(currentPoint);
if (nextPoint) ripplePoints.push(nextPoint);
if (currentPoint) {
ripplePoints.push({
...currentPoint,
color: lineColor // 添加颜色信息
});
}
if (nextPoint) {
ripplePoints.push({
...nextPoint,
color: nextItem.color || lineColor // 添加颜色信息
});
}
// 检查是否应该绘制连线
if (currentItem.isLine !== false) {
solidData[0]?.[1].push(
getLineItem(countryCode, nextCountryCode)
);
const lineItem = getLineItem(countryCode, nextCountryCode);
// 添加颜色信息到连线数据
solidData[0][1].push({
...lineItem,
color: lineColor // 添加颜色信息
} as any);
}
}
}
@ -288,39 +395,40 @@ export const WorldGeo = memo(
solidData,
otherLineList,
ripplePoints,
pointColors
};
};
// 获取连线经纬度数据
const convertData = (data: LinesDataType[]) => {
const res = [];
const midpoints = [];
for (let index = 0; index < data.length; index++) {
const dataIndex = data[index];
const fromCoord =
geoCoordMap[dataIndex?.[0]?.country_code ?? ""];
const fromCoord = geoCoordMap[dataIndex?.[0]?.country_code ?? ""];
const toCoord = geoCoordMap[dataIndex?.[1]?.country_code ?? ""];
const fromCountry = dataIndex?.[0]?.country_code ?? "";
const toCountry = dataIndex?.[1]?.country_code ?? "";
const lineColor = (dataIndex as any)?.color || "#0ea5e9"; // 获取线条颜色
if (fromCoord && toCoord) {
res.push([fromCoord, toCoord]);
res.push({
coords: [fromCoord, toCoord],
lineStyle: {
color: lineColor // 使用自定义颜色
}
});
// 计算中点,考虑曲线的弧度
const curveness = -0.4; // 与飞线弧度相同
const x1 = fromCoord[0];
const y1 = fromCoord[1];
const x2 = toCoord[0];
const y2 = toCoord[1];
// 计算控制点
const cpx = (x1 + x2) / 2 - (y2 - y1) * curveness;
const cpy = (y1 + y2) / 2 - (x1 - x2) * curveness;
// 计算曲线上的中点 (t=0.5 时的贝塞尔曲线点)
const midX = x1 * 0.25 + cpx * 0.5 + x2 * 0.25;
const midY = y1 * 0.25 + cpy * 0.5 + y2 * 0.25;
midpoints.push({
id: `line-label-${index}`,
midpoint: [midX, midY],
@ -329,16 +437,15 @@ export const WorldGeo = memo(
});
}
}
// 更新中点引用
// lineMidpointsRef.current = midpoints;
return res;
};
// 创建双层点效果 - 大点
const createDualLayerPoint = (
lastExit: LinesItemType,
isMainPath: boolean = true
isMainPath: boolean = true,
color?: string
) => {
// 创建数据数组,用于两个散点图层
const pointData = lastExit
@ -355,8 +462,8 @@ export const WorldGeo = memo(
// 根据是否是主路径设置不同的大小和颜色
const outerSize = isMainPath ? 8 : 4;
const innerSize = isMainPath ? 4 : 2;
// Use selectedApp.color if available, otherwise default to blue
const outerColor = selectedApp?.color || "#0ea5e9"; // Use selectedApp.color with fallback
// 使用传入的颜色或默认颜色
const outerColor = color || "#0ea5e9";
const innerColor = "#FFFFFF"; // 白色内层
return [
{
@ -417,6 +524,37 @@ export const WorldGeo = memo(
} as echarts.SeriesOption,
];
};
// 添加新方法:根据经纬度数组创建蓝色涟漪小点(不包含白色内层点)
const createRipplePointsFromCoordinates = (
coordinates: [number, number][],
series: echarts.SeriesOption[]
) => {
if (!coordinates || coordinates.length === 0) return;
// 使用selectedApp.color或默认蓝色
const outerColor = "#01FF5E";
// 只创建外层带涟漪效果的点
series.push({
type: "effectScatter",
coordinateSystem: "geo",
zlevel: 3,
color: outerColor,
symbol: "circle",
symbolSize: 6,
rippleEffect: {
period: 8, // 动画时间
brushType: "stroke", // 波纹绘制方式
scale: 6, // 波纹圆环最大限制
brushWidth: 2,
},
label: {
show: false,
},
data: coordinates.map((coord) => ({
name: "", // 可以根据需要添加名称
value: coord,
})),
} as echarts.SeriesOption);
};
// 创建路径点的双层效果
const createPathPoints = (
dataItems: LinesDataType[],
@ -424,27 +562,32 @@ export const WorldGeo = memo(
) => {
// 创建数据数组
const pointData = dataItems.map((dataItem: LinesDataType) => {
const color = (dataItem as any).color || "#0ea5e9"; // 获取颜色信息
return {
name: dataItem[0].name,
value: geoCoordMap[dataItem[0].country_code],
datas: {
country_code: dataItem[0].country_code,
color: color // 保存颜色信息
},
};
});
// 根据是否是主路径设置不同的大小和颜色
const outerSize = isMainPath ? 8 : 4;
const innerSize = isMainPath ? 4 : 2;
// Use selectedApp.color if available, otherwise default to blue
const outerColor = selectedApp?.color || "#0ea5e9"; // Use selectedApp.color with fallback
const innerColor = "#FFFFFF"; // 白色内层
return [
{
// 外层蓝色点,带涟漪效果
// 外层彩色点,带涟漪效果
type: "effectScatter",
coordinateSystem: "geo",
zlevel: 3,
color: outerColor,
// 使用回调函数根据数据项设置颜色
itemStyle: {
color: function(params: any) {
return params.data.datas.color || "#0ea5e9";
}
},
symbol: "circle",
symbolSize: outerSize,
rippleEffect: {
@ -460,9 +603,6 @@ export const WorldGeo = memo(
show: false,
trigger: "item",
formatter: (params: any) => {
// const countryCode = params.data.datas.country_code;
// const countryName = params.data.name;
// 创建自定义HTML提示框
return `
<div class="tip-box">
<img class="close-icon" src="${getUrl(
@ -497,13 +637,16 @@ export const WorldGeo = memo(
};
// 创建带自定义提示框的涟漪点
const createRipplePointsWithTooltip = (ripplePoints: any) => {
// Use selectedApp.color if available, otherwise default to blue
const outerColor = selectedApp?.color || "#0ea5e9"; // Use selectedApp.color with fallback
return {
type: "effectScatter",
coordinateSystem: "geo",
zlevel: 3,
color: outerColor,
// 使用回调函数根据数据项设置颜色
itemStyle: {
color: function(params: any) {
return params.data.datas.color || "#0ea5e9";
}
},
symbol: "circle",
symbolSize: 8,
rippleEffect: {
@ -524,9 +667,6 @@ export const WorldGeo = memo(
show: false,
trigger: "item",
formatter: (params: any) => {
// const countryCode = params.data.datas.country_code;
// const countryName = params.data.name;
// 创建自定义HTML提示框
return `
<div class="tip-box">
<img class="close-icon" src="${getUrl(
@ -547,6 +687,7 @@ export const WorldGeo = memo(
value: point.value,
datas: {
country_code: point.country_code,
color: point.color || "#0ea5e9" // 保存颜色信息
},
})),
} as echarts.SeriesOption;
@ -556,13 +697,13 @@ export const WorldGeo = memo(
const { solidData, otherLineList, ripplePoints } = getLine();
// 如果有需要显示涟漪效果的点,添加它们
if (ripplePoints.length > 0) {
// 添加带自定义提示框的外层色点
// 添加带自定义提示框的外层色点
series.push(createRipplePointsWithTooltip(ripplePoints));
// 添加内层白色点,不带涟漪效果
series.push({
type: "scatter", // 使用普通scatter不带特效
coordinateSystem: "geo",
zlevel: 4, // 确保在色点上方
zlevel: 4, // 确保在色点上方
color: "#FFFFFF", // 白色内层
symbol: "circle",
symbolSize: 4,
@ -584,6 +725,8 @@ export const WorldGeo = memo(
return;
}
const lastExit = item[1]?.[item[1].length - 1]?.[1] ?? null;
const lastExitColor = (item[1]?.[item[1].length - 1] as any)?.color || "#0ea5e9";
// 添加飞行线
series.push({
name: item[0],
@ -604,26 +747,25 @@ export const WorldGeo = memo(
lineStyle: {
curveness: -0.4, // 飞线弧度
type: "solid", // 飞线类型
color: selectedApp?.color || "#0ea5e9", // Use selectedApp.color with fallback
width: 1.5, // 飞线宽度
opacity: 0.1,
},
data: convertData(
item[1]
) as echarts.LinesSeriesOption["data"],
data: convertData(item[1]) as echarts.LinesSeriesOption["data"],
});
// 添加路径点的双层效果
const pathPoints = createPathPoints(item[1], true);
series.push(...pathPoints);
// 添加出口节点的双层效果
if (lastExit) {
const exitNodes = createDualLayerPoint(lastExit, true);
const exitNodes = createDualLayerPoint(lastExit, true, lastExitColor);
series.push(...exitNodes);
}
});
otherLineList.forEach((line: any) => {
line.forEach((item: any) => {
const lastExit = item[1]?.[item[1].length - 1]?.[1] ?? null;
const lastExitColor = (item[1]?.[item[1].length - 1] as any)?.color || "#0ea5e9";
// 添加虚线
series.push({
name: item[0],
@ -640,16 +782,14 @@ export const WorldGeo = memo(
width: 0.5, // 飞线宽度
opacity: 0.6,
},
data: convertData(
item[1]
) as echarts.LinesSeriesOption["data"],
data: convertData(item[1]) as echarts.LinesSeriesOption["data"],
});
// 添加路径点的双层效果(次要路径)
const pathPoints = createPathPoints(item[1], false);
series.push(...pathPoints);
// 添加出口节点的双层效果(次要路径)
if (lastExit) {
const exitNodes = createDualLayerPoint(lastExit, false);
const exitNodes = createDualLayerPoint(lastExit, false, lastExitColor);
series.push(...exitNodes);
}
});
@ -687,20 +827,10 @@ export const WorldGeo = memo(
}|${name}}{gap3|}{right|}`;
},
rich: {
// left: {
// color: "transparent",
// height: 35,
// width: 8,
// align: "center",
// backgroundColor: {
// image: `data:image/svg+xml;charset=utf-8;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMSIgaGVpZ2h0PSI1OCIgdmlld0JveD0iMCAwIDExIDU4IiBmaWxsPSJub25lIj4KPHBhdGggZD0iTTExIDU2LjA4ODRIMVY0Ni4wODg0IiBzdHJva2U9IiNGRkZERDMiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIi8+CjxwYXRoIGQ9Ik0xIDExVjFIMTEiIHN0cm9rZT0iI0ZGRkREMyIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiLz4KPHBhdGggZD0iTTguNzI5NDkgMTlWMzkiIHN0cm9rZT0iI0ZGRkREMyIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiLz4KPC9zdmc+`, // 动态生成国旗图标
// },
// },
gap1: {
height: 35,
width: 8,
},
gap2: {
height: 35,
width: 6,
@ -727,15 +857,6 @@ export const WorldGeo = memo(
height: 35,
width: 8,
},
// right: {
// color: "transparent",
// height: 35,
// width: 8,
// align: "center",
// backgroundColor: {
// image: `data:image/svg+xml;charset=utf-8;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNCIgaGVpZ2h0PSI1OCIgdmlld0JveD0iMCAwIDE0IDU4IiBmaWxsPSJub25lIj4KPHBhdGggZD0iTTEyLjczMDIgNDYuMDQzOVY1Ni4wNDM5SDIuNzMwMjIiIHN0cm9rZT0iI0ZGRkREMyIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiLz4KPHBhdGggZD0iTTIuNzMwMjIgMS4wNDM5NUgxMi43MzAyVjExLjA0MzkiIHN0cm9rZT0iI0ZGRkREMyIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiLz4KPHBhdGggZD0iTTEgMTkuMDQzOVYzOS4wNDM5IiBzdHJva2U9IiNGRkZERDMiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIi8+Cjwvc3ZnPg==`, // 动态生成国旗图标
// },
// },
},
backgroundColor: "transparent",
},
@ -744,14 +865,12 @@ export const WorldGeo = memo(
}
);
};
// 创建A点和B点并添加飞线和标签
const createSpecialPoints = (series: echarts.SeriesOption[]) => {
// 定义点A和点B的坐标
const pointA = [-42.604303, 71.706936];
const pointB = [-106.346771, 56.130366];
const newPointB = [pointB[0] + 14, pointB[1] + 10];
// 添加A点 - 带涟漪效果的双层点
series.push(
// 外层带涟漪效果的点
@ -794,7 +913,6 @@ export const WorldGeo = memo(
},
],
} as echarts.SeriesOption,
// 内层白色点
{
type: "scatter", // 普通scatter不带特效
@ -814,7 +932,6 @@ export const WorldGeo = memo(
],
} as echarts.SeriesOption
);
// 添加B点 - 大型圆形区域
series.push({
type: "scatter",
@ -849,7 +966,6 @@ export const WorldGeo = memo(
},
],
} as echarts.SeriesOption);
// 添加A到B的飞线无特效
series.push({
type: "lines",
@ -870,22 +986,18 @@ export const WorldGeo = memo(
},
],
} as echarts.SeriesOption);
// 计算飞线中点坐标(考虑曲率)
const x1 = pointA[0];
const y1 = pointA[1];
const x2 = newPointB[0];
const y2 = newPointB[1];
const curveness = -0.4;
// 计算控制点
const cpx = (x1 + x2) / 2 - (y2 - y1) * curveness;
const cpy = (y1 + y2) / 2 - (x1 - x2) * curveness;
// 计算曲线上的中点 (t=0.5 时的贝塞尔曲线点)
const midX = x1 * 0.25 + cpx * 0.5 + x2 * 0.25;
const midY = y1 * 0.25 + cpy * 0.5 + y2 * 0.25;
// 将中点添加到 lineMidpointsRef 中,以便使用 DOM 方式创建标签
lineMidpointsRef.current.push({
id: "special-line-label",
@ -893,16 +1005,19 @@ export const WorldGeo = memo(
fromCountry: "A",
toCountry: "B",
});
return series;
};
const getOption = () => {
const series: echarts.SeriesOption[] = [];
getLianData(series);
// getMianLineTipData(series); // 添加主线tip 暂时隐藏
createSpecialPoints(series); // 添加特殊点和飞线
if (newHomeProxies[0]?.authenticationPoint) {
createRipplePointsFromCoordinates(
newHomeProxies[0]?.authenticationPoint || [],
series
);
}
const option = {
backgroundColor: "transparent",
// 全局提示框配置
@ -969,8 +1084,7 @@ export const WorldGeo = memo(
}
| undefined;
}) => {
if (parameters.data?.name)
return parameters.data.name;
if (parameters.data?.name) return parameters.data.name;
return parameters.name;
},
},
@ -979,7 +1093,6 @@ export const WorldGeo = memo(
};
return option;
};
// 创建DOM标签
const createDOMLabels = () => {
// 清除现有标签
@ -998,7 +1111,6 @@ export const WorldGeo = memo(
container.style.width = "100%";
container.style.height = "100%";
container.style.overflow = "hidden";
// 添加到地图容器
const chartDom = document.getElementById("screenGeo");
if (chartDom) {
@ -1007,7 +1119,6 @@ export const WorldGeo = memo(
labelContainerRef.current = container;
}
}
// 创建新标签
lineMidpointsRef.current.forEach((point, index) => {
const label = document.createElement("div");
@ -1019,7 +1130,6 @@ export const WorldGeo = memo(
label.style.whiteSpace = "nowrap";
label.style.pointerEvents = "none";
label.style.zIndex = "1001";
// 特殊线标签A到B的线
if (point.id === "special-line-label") {
label.style.backgroundColor = "#8B3700";
@ -1040,24 +1150,19 @@ export const WorldGeo = memo(
label.style.fontWeight = "normal";
label.textContent = "SS签名";
}
// 添加到容器
labelContainerRef.current?.appendChild(label);
labelsRef.current.push(label);
});
// 更新标签位置
updateLabelPositions();
};
// 更新标签位置
const updateLabelPositions = () => {
if (!proxyGeoRef.current || !labelContainerRef.current) return;
lineMidpointsRef.current.forEach((point, index) => {
const label = labelsRef.current[index];
if (!label) return;
const pixelPoint = proxyGeoRef.current?.convertToPixel(
"geo",
point.midpoint
@ -1068,64 +1173,64 @@ export const WorldGeo = memo(
}
});
};
const handleResize = () => {
proxyGeoRef.current?.resize();
updateLabelPositions();
};
useEffect(() => {
preMainToData.current?.some(
(item, index) =>
item.country_code !== mainToData[index]?.country_code
(item, index) => item.country_code !== mainToData[index]?.country_code
) && proxyGeoRef.current?.clear();
preMainToData.current = mainToData;
const option = getOption();
proxyGeoRef.current?.setOption(option);
// 创建DOM标签
setTimeout(createDOMLabels, 100);
}, [newHomeProxies, mainToData]);
useEffect(() => {
const chartDom = document.getElementById("screenGeo");
proxyGeoRef.current = echarts.init(chartDom);
echarts.registerMap(
"world",
worldGeoJson as unknown as Parameters<
typeof echarts.registerMap
>[1]
worldGeoJson as unknown as Parameters<typeof echarts.registerMap>[1]
);
const option = getOption();
option && proxyGeoRef.current?.setOption(option);
// 添加地图交互事件监听器
proxyGeoRef.current?.on("georoam", updateLabelPositions);
// 页面resize时触发
window.addEventListener("resize", handleResize);
return () => {
window.removeEventListener("resize", handleResize);
proxyGeoRef.current?.off("georoam", updateLabelPositions);
// 清理DOM标签
if (labelContainerRef.current) {
labelContainerRef.current.remove();
labelContainerRef.current = null;
labelsRef.current = [];
}
proxyGeoRef.current?.dispose();
proxyGeoRef.current = null;
};
}, []);
useEffect(() => {
console.log(tooltipClosed,'tooltipClosedtooltipClosed')
if (tooltipClosed) {
createCustomTooltip();
createCustomTooltip2();
} else {
customTooltipRef.current?.remove();
customTooltip2Ref.current?.remove();
customTooltipRef.current = null;
customTooltip2Ref.current = null;
}
return () => {
customTooltipRef.current?.remove();
customTooltip2Ref.current?.remove();
customTooltipRef.current = null;
customTooltip2Ref.current = null;
};
}, [tooltipClosed, tooltipType]);
return (
<div className="flex-1 h-full flex flex-col">
<div id="screenGeo" className="flex-1"></div>

View File

@ -9,9 +9,7 @@ import Web3Box2Png from "@/assets/image/home/web3-box2.png";
import OpenProxyPng from "@/assets/image/home/open-proxy.png";
import web3BoxGif from "@/assets/gif/web3-box-bg.gif";
import VectorSlideSvg from "@/assets/svg/home/vector-solide.svg?react";
import AddSvg from "@/assets/svg/home/add.svg?react";
import InterSvg from "@/assets/svg/home/inter.svg?react";
import TrashSvg from "@/assets/svg/home/trash.svg?react";
import { Apps, CONST_TOOLTIP_TYPE } from "@/pages/anti-forensics-forwarding";
import { cn } from "@/lib/utils";
@ -20,13 +18,13 @@ import {
setProxyInfoProxies,
setProxiesList1,
setProxiesList2,
setProxiesLine,
} from "@/store/web3Slice";
import type { AppDispatch, RootState } from "@/store";
import "./index.scss";
import { DialogConfig, FormAlertDialog } from "./components/FormAlertDialog";
import { ClearNodeDialog } from "./components/ClearNodeDialog";
import { blockChainApi } from "@/api/block";
import { APP_DIVERSION } from "../anti-forensics-forwarding/data/mockData";
export const DIALOGTYPE = {
ADDNode: {
@ -71,12 +69,24 @@ const NewHome = () => {
const [blockChain, setBlockChain] = useState<any>(null);
const [tooltipClosed, setTooltipClosed] = useState(false);
const [tooltipClosed, setTooltipClosed] = useState(true);
const [tooltipType, setTooltipType] = useState(
CONST_TOOLTIP_TYPE.NESTED_ENCRYPTION.type
);
const appDiversion = useMemo(() => {
return Apps.map((item) => {
const findApp = APP_DIVERSION.find(
(appItem) => item.name === appItem.name
);
return {
...item,
...findApp,
};
});
}, []);
const newWeb3List = useMemo(() => {
// 展示最新的6个节点
return web3List.slice(-6);
@ -102,7 +112,6 @@ const NewHome = () => {
};
const handleClickApp = (item: any) => {
console.log("item", item);
setSelectedApp(item);
};
@ -178,12 +187,14 @@ const NewHome = () => {
console.log("res",res)
setBlockChain(res)
})
},[])
return (
<div className="decentralized w-full h-full flex flex-col relative">
<div className="box"></div>
<div className="text-red-300">{blockChain?.block_id?.hash || "111"}</div>
<div className="w-full flex items-center justify-center relative">
{/* <img
className="w-[1693px] h-[271px] absolute top-[160px] left-[50%] translate-x-[-50%] z-10"
@ -307,7 +318,7 @@ const NewHome = () => {
</div>
<div className="absolute bottom-[10px] left-[50%] translate-x-[-50%] w-[calc(100%-51px)] p-6 inline-flex justify-start items-center gap-10">
<img src={OpenProxyPng} className="w-[193px] h-[90px] cursor-pointer" alt="" />
<div
{/* <div
className="bt1 cursor-pointer"
onClick={() => {
setTooltipType(
@ -328,8 +339,8 @@ const NewHome = () => {
}}
>
</div>
{Apps.map((item) => {
</div> */}
{appDiversion.map((item) => {
return (
<div
key={item.name}

View File

@ -32,9 +32,7 @@ interface Iweb3Slice {
const randomBalance = (): string => {
const value = Math.random() * 100;
// 50% 的概率返回整数50% 的概率返回一位小数
return Math.random() > 0.5
? Math.floor(value).toString()
: value.toFixed(1);
return Math.random() > 0.5 ? Math.floor(value).toString() : value.toFixed(1);
};
// 随机生成 1-60 的整数
@ -110,6 +108,23 @@ const initialState: Iweb3Slice = {
clearFailTimer: null,
newHomeProxies: [
{
authenticationPoint: [
[-103.346771, 54.130366],
[-120.346771, 52.130366],
[-108.346771, 48.130366],
[-98.346771, 46.130366],
[-106.346771, 48.450366],
[-101.346771, 53.130366],
[-123.346771, 58.130366],
[-111.346771, 65.443366],
[-108.346771, 54.130366],
[-116.346771, 59.130366],
[-97.346771, 61.130366],
[-95.346771, 63.130366],
[-113.346771, 58.840366],
[-99.346771, 59.130366],
[-102.346771, 68.130366],
],
data: [
{
country_code: "gl",
@ -134,7 +149,7 @@ const initialState: Iweb3Slice = {
],
isLine: true,
name: "newHomeProxies",
}
},
],
};
@ -174,12 +189,10 @@ export const appSlice = createSlice({
if (state.proxy_info.proxies.length === 0) return;
// 标记所有代理为在线 - 这个操作仍然需要
state.proxy_info.proxies = state.proxy_info.proxies.map(
(item: any) => {
state.proxy_info.proxies = state.proxy_info.proxies.map((item: any) => {
item.isLine = true;
return item;
}
);
});
// 计算需要添加的钱包数量
const proxiesCount = state.proxy_info.proxies.length;
@ -249,9 +262,7 @@ export const appSlice = createSlice({
// state.proxy_info.proxies = action.payload;
},
randomUpdateWeb3List: (state) => {
state.web3List = state.web3List.map((wallet) =>
updateWalletData(wallet)
);
state.web3List = state.web3List.map((wallet) => updateWalletData(wallet));
},
randomUpdateWeb3List2: (state) => {
state.web3List2 = state.web3List2.map((wallet) =>