feat:新增mockData,以及一些逻辑的修复
This commit is contained in:
parent
ae04f35bb1
commit
da91340bcf
3
src/assets/svg/anti-forensics-forwarding/LineLeft.svg
Normal file
3
src/assets/svg/anti-forensics-forwarding/LineLeft.svg
Normal 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 |
@ -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>
|
||||
|
||||
447
src/pages/anti-forensics-forwarding/data/mockData.ts
Normal file
447
src/pages/anti-forensics-forwarding/data/mockData.ts
Normal 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,
|
||||
};
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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,58 +356,79 @@ 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 虚线数据处理(保持原有逻辑)
|
||||
const otherLineList :any = [];
|
||||
const otherLineList: any = [];
|
||||
return {
|
||||
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>
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -20,7 +20,7 @@ export interface Iweb3 {
|
||||
interface Iweb3Slice {
|
||||
web3List: Iweb3[]; // 代表状态一
|
||||
web3List2: Iweb3[]; // 代表状态二
|
||||
newHomeProxies:any[];
|
||||
newHomeProxies: any[];
|
||||
path_list: any;
|
||||
proxy_info: any;
|
||||
clearWarningTimer: number | null;
|
||||
@ -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 的整数
|
||||
@ -108,8 +106,25 @@ const initialState: Iweb3Slice = {
|
||||
},
|
||||
clearWarningTimer: null,
|
||||
clearFailTimer: null,
|
||||
newHomeProxies:[
|
||||
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) =>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user