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 worldGeoJson from "@/assets/echarts-map/json/world.json";
|
||||||
import { geoCoordMap, countryNameMap, countryCodeMap } from "@/data";
|
import { geoCoordMap, countryNameMap, countryCodeMap } from "@/data";
|
||||||
import { getUrl } from "@/lib/utils";
|
import { getUrl } from "@/lib/utils";
|
||||||
import { CONST_TOOLTIP_TYPE } from "..";
|
import { CONST_TOOLTIP_TYPE } from "@/pages/anti-forensics-forwarding";
|
||||||
const planePathImg =
|
const planePathImg =
|
||||||
"image://data:image/svg+xml;charset=utf-8;base64,PHN2ZyB3aWR0aD0iNjciIGhlaWdodD0iMTAyIiB2aWV3Qm94PSIwIDAgNjcgMTAyIiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8ZyBmaWx0ZXI9InVybCgjZmlsdGVyMF9mXzYxMTdfMjEyNDA3KSI+CjxwYXRoIGQ9Ik0zNC4yMTA5IDkxLjE4ODZMNTMuNjU3OCA0MC45NThDNTQuOTM4IDM3LjY1MTMgNTUuNzk4MyAzNC4xNTkyIDU1LjM1NjMgMzAuNjQxQzU0LjQzNTcgMjMuMzEyOCA1MC40Njg0IDExLjAyMDggMzQuMjExMiAxMS4wMjA4QzE5LjE5MDMgMTEuMDIwOCAxMy45MTEgMjEuNTE0NiAxMi4wNTU0IDI4Ljg5MTJDMTAuOTAxIDMzLjQ4MDYgMTEuOTkyNiAzOC4yMTg2IDEzLjgyMzEgNDIuNTgyN0wzNC4yMTA5IDkxLjE4ODZaIiBmaWxsPSJ1cmwoI3BhaW50MF9saW5lYXJfNjExN18yMTI0MDcpIi8+CjwvZz4KPGRlZnM+CjxmaWx0ZXIgaWQ9ImZpbHRlcjBfZl82MTE3XzIxMjQwNyIgeD0iMC44OTE3NDQiIHk9IjAuMzMxOTkiIHdpZHRoPSI2NS4yNzA3IiBoZWlnaHQ9IjEwMS41NDUiIGZpbHRlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIj4KPGZlRmxvb2QgZmxvb2Qtb3BhY2l0eT0iMCIgcmVzdWx0PSJCYWNrZ3JvdW5kSW1hZ2VGaXgiLz4KPGZlQmxlbmQgbW9kZT0ibm9ybWFsIiBpbj0iU291cmNlR3JhcGhpYyIgaW4yPSJCYWNrZ3JvdW5kSW1hZ2VGaXgiIHJlc3VsdD0ic2hhcGUiLz4KPGZlR2F1c3NpYW5CbHVyIHN0ZERldmlhdGlvbj0iNS4zNDQ0MSIgcmVzdWx0PSJlZmZlY3QxX2ZvcmVncm91bmRCbHVyXzYxMTdfMjEyNDA3Ii8+CjwvZmlsdGVyPgo8bGluZWFyR3JhZGllbnQgaWQ9InBhaW50MF9saW5lYXJfNjExN18yMTI0MDciIHgxPSIzNS4yODI2IiB5MT0iMTAuODU2NCIgeDI9IjM1LjI4MjYiIHkyPSI4Ni44NTY0IiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CjxzdG9wIHN0b3AtY29sb3I9IiMwMEYyRkYiLz4KPHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjMTUwMEZGIi8+CjwvbGluZWFyR3JhZGllbnQ+CjwvZGVmcz4KPC9zdmc+Cg==";
|
"image://data:image/svg+xml;charset=utf-8;base64,PHN2ZyB3aWR0aD0iNjciIGhlaWdodD0iMTAyIiB2aWV3Qm94PSIwIDAgNjcgMTAyIiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8ZyBmaWx0ZXI9InVybCgjZmlsdGVyMF9mXzYxMTdfMjEyNDA3KSI+CjxwYXRoIGQ9Ik0zNC4yMTA5IDkxLjE4ODZMNTMuNjU3OCA0MC45NThDNTQuOTM4IDM3LjY1MTMgNTUuNzk4MyAzNC4xNTkyIDU1LjM1NjMgMzAuNjQxQzU0LjQzNTcgMjMuMzEyOCA1MC40Njg0IDExLjAyMDggMzQuMjExMiAxMS4wMjA4QzE5LjE5MDMgMTEuMDIwOCAxMy45MTEgMjEuNTE0NiAxMi4wNTU0IDI4Ljg5MTJDMTAuOTAxIDMzLjQ4MDYgMTEuOTkyNiAzOC4yMTg2IDEzLjgyMzEgNDIuNTgyN0wzNC4yMTA5IDkxLjE4ODZaIiBmaWxsPSJ1cmwoI3BhaW50MF9saW5lYXJfNjExN18yMTI0MDcpIi8+CjwvZz4KPGRlZnM+CjxmaWx0ZXIgaWQ9ImZpbHRlcjBfZl82MTE3XzIxMjQwNyIgeD0iMC44OTE3NDQiIHk9IjAuMzMxOTkiIHdpZHRoPSI2NS4yNzA3IiBoZWlnaHQ9IjEwMS41NDUiIGZpbHRlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIj4KPGZlRmxvb2QgZmxvb2Qtb3BhY2l0eT0iMCIgcmVzdWx0PSJCYWNrZ3JvdW5kSW1hZ2VGaXgiLz4KPGZlQmxlbmQgbW9kZT0ibm9ybWFsIiBpbj0iU291cmNlR3JhcGhpYyIgaW4yPSJCYWNrZ3JvdW5kSW1hZ2VGaXgiIHJlc3VsdD0ic2hhcGUiLz4KPGZlR2F1c3NpYW5CbHVyIHN0ZERldmlhdGlvbj0iNS4zNDQ0MSIgcmVzdWx0PSJlZmZlY3QxX2ZvcmVncm91bmRCbHVyXzYxMTdfMjEyNDA3Ii8+CjwvZmlsdGVyPgo8bGluZWFyR3JhZGllbnQgaWQ9InBhaW50MF9saW5lYXJfNjExN18yMTI0MDciIHgxPSIzNS4yODI2IiB5MT0iMTAuODU2NCIgeDI9IjM1LjI4MjYiIHkyPSI4Ni44NTY0IiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CjxzdG9wIHN0b3AtY29sb3I9IiMwMEYyRkYiLz4KPHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjMTUwMEZGIi8+CjwvbGluZWFyR3JhZGllbnQ+CjwvZGVmcz4KPC9zdmc+Cg==";
|
||||||
interface LinesItemType {
|
interface LinesItemType {
|
||||||
name: string;
|
name: string;
|
||||||
country_code: string;
|
country_code: string;
|
||||||
value: number[];
|
value: number[];
|
||||||
|
color?: string; // 添加颜色属性
|
||||||
}
|
}
|
||||||
type LinesDataType = [LinesItemType, LinesItemType];
|
type LinesDataType = [LinesItemType, LinesItemType];
|
||||||
type LinesType = [string, LinesDataType[]];
|
type LinesType = [string, LinesDataType[]];
|
||||||
// 创建单个国家的涟漪效果
|
// 创建单个国家的涟漪效果
|
||||||
const createCountryRipple = (countryCode: string) => {
|
const createCountryRipple = (countryCode: string, color?: string) => {
|
||||||
const coords = geoCoordMap[countryCode];
|
const coords = geoCoordMap[countryCode];
|
||||||
if (!coords) return null;
|
if (!coords) return null;
|
||||||
return {
|
return {
|
||||||
name: countryCodeMap[countryCode] ?? "",
|
name: countryCodeMap[countryCode] ?? "",
|
||||||
value: coords,
|
value: coords,
|
||||||
country_code: countryCode,
|
country_code: countryCode,
|
||||||
|
color: color || "#0ea5e9", // 添加颜色属性,如果没有则使用默认颜色
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
export const WorldGeo = memo(
|
export const WorldGeo = memo(
|
||||||
({
|
({
|
||||||
screenData,
|
currentValue,
|
||||||
|
newHomeProxies,
|
||||||
selectedApp,
|
selectedApp,
|
||||||
tooltipType,
|
tooltipType,
|
||||||
tooltipClosed,
|
tooltipClosed,
|
||||||
setTooltipClosed,
|
setTooltipClosed,
|
||||||
}: {
|
}: {
|
||||||
screenData: any;
|
currentValue: any;
|
||||||
|
newHomeProxies: any;
|
||||||
selectedApp: any;
|
selectedApp: any;
|
||||||
tooltipType: string;
|
tooltipType: string;
|
||||||
tooltipClosed: boolean;
|
tooltipClosed: boolean;
|
||||||
setTooltipClosed: (value: boolean) => void;
|
setTooltipClosed: (value: boolean) => void;
|
||||||
}) => {
|
}) => {
|
||||||
// const queryClient = useQueryClient()
|
// const queryClient = useQueryClient()
|
||||||
|
// 嵌套加密ref
|
||||||
const customTooltipRef = useRef<HTMLDivElement | null>(null);
|
const customTooltipRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
// 流量混淆ref
|
||||||
|
const customTooltip2Ref = useRef<HTMLDivElement | null>(null);
|
||||||
const proxyGeoRef = useRef<EChartsType | null>(null);
|
const proxyGeoRef = useRef<EChartsType | null>(null);
|
||||||
const preMainToData = useRef<{ country_code: string }[]>([]);
|
const preMainToData = useRef<{ country_code: string }[]>([]);
|
||||||
const lineMidpointsRef = useRef<
|
const lineMidpointsRef = useRef<
|
||||||
@ -54,13 +61,9 @@ export const WorldGeo = memo(
|
|||||||
>([]);
|
>([]);
|
||||||
const labelContainerRef = useRef<HTMLDivElement | null>(null);
|
const labelContainerRef = useRef<HTMLDivElement | null>(null);
|
||||||
const labelsRef = useRef<HTMLDivElement[]>([]);
|
const labelsRef = useRef<HTMLDivElement[]>([]);
|
||||||
|
|
||||||
const mainToData = useMemo(() => {
|
const mainToData = useMemo(() => {
|
||||||
// 使用新的数据结构
|
// 使用新的数据结构
|
||||||
const proxiesList =
|
const proxiesList = currentValue ?? [];
|
||||||
selectedApp && selectedApp?.jumpList
|
|
||||||
? [selectedApp.jumpList]
|
|
||||||
: screenData?.proxy_info?.proxies ?? [];
|
|
||||||
// 初始化数据数组 - 不再包含 startCountry
|
// 初始化数据数组 - 不再包含 startCountry
|
||||||
const data: any = [];
|
const data: any = [];
|
||||||
// 遍历代理列表
|
// 遍历代理列表
|
||||||
@ -76,25 +79,28 @@ export const WorldGeo = memo(
|
|||||||
country_code: item.country_code,
|
country_code: item.country_code,
|
||||||
type: "start",
|
type: "start",
|
||||||
isLine: proxyItem.isLine, // 保存连线标志
|
isLine: proxyItem.isLine, // 保存连线标志
|
||||||
|
color: proxyItem.color || item.color, // 添加颜色属性
|
||||||
});
|
});
|
||||||
// 添加终点(ingress_country_code)
|
// 添加终点(ingress_country_code)
|
||||||
data.push({
|
data.push({
|
||||||
country_code: item.ingress_country_code,
|
country_code: item.ingress_country_code,
|
||||||
type: "end",
|
type: "end",
|
||||||
isLine: proxyItem.isLine, // 保存连线标志
|
isLine: proxyItem.isLine, // 保存连线标志
|
||||||
|
color: proxyItem.color || item.color, // 添加颜色属性
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// 如果没有 ingress_country_code,只添加 country_code
|
// 如果没有 ingress_country_code,只添加 country_code
|
||||||
data.push({
|
data.push({
|
||||||
country_code: item.country_code,
|
country_code: item.country_code,
|
||||||
isLine: proxyItem.isLine, // 保存连线标志
|
isLine: proxyItem.isLine, // 保存连线标志
|
||||||
|
color: proxyItem.color || item.color, // 添加颜色属性
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return data;
|
return data;
|
||||||
}, [screenData, selectedApp]);
|
}, [currentValue]);
|
||||||
// 创建自定义提示框DOM元素
|
// 创建自定义提示框DOM元素
|
||||||
const createCustomTooltip = () => {
|
const createCustomTooltip = () => {
|
||||||
// 如果已经存在自定义提示框,则移除它
|
// 如果已经存在自定义提示框,则移除它
|
||||||
@ -110,9 +116,8 @@ export const WorldGeo = memo(
|
|||||||
tooltip.style.backgroundColor = "transparent";
|
tooltip.style.backgroundColor = "transparent";
|
||||||
// 设置提示框内容
|
// 设置提示框内容
|
||||||
const currentTooltipType =
|
const currentTooltipType =
|
||||||
CONST_TOOLTIP_TYPE[
|
CONST_TOOLTIP_TYPE[tooltipType as keyof typeof CONST_TOOLTIP_TYPE] ||
|
||||||
tooltipType as keyof typeof CONST_TOOLTIP_TYPE
|
CONST_TOOLTIP_TYPE.NESTED_ENCRYPTION;
|
||||||
] || CONST_TOOLTIP_TYPE.NESTED_ENCRYPTION;
|
|
||||||
tooltip.innerHTML = `
|
tooltip.innerHTML = `
|
||||||
<div class="tooltip-content">
|
<div class="tooltip-content">
|
||||||
<img class="line-img" src="${getUrl(
|
<img class="line-img" src="${getUrl(
|
||||||
@ -136,8 +141,7 @@ export const WorldGeo = memo(
|
|||||||
? "encryption-img"
|
? "encryption-img"
|
||||||
: "traffic-obfuscation-img"
|
: "traffic-obfuscation-img"
|
||||||
}" src="${getUrl(
|
}" src="${getUrl(
|
||||||
CONST_TOOLTIP_TYPE.NESTED_ENCRYPTION.type ===
|
CONST_TOOLTIP_TYPE.NESTED_ENCRYPTION.type === currentTooltipType.type
|
||||||
currentTooltipType.type
|
|
||||||
? "image/nested-encryption.png"
|
? "image/nested-encryption.png"
|
||||||
: "image/traffic-obfuscation.png"
|
: "image/traffic-obfuscation.png"
|
||||||
)}" alt="" />
|
)}" alt="" />
|
||||||
@ -164,25 +168,94 @@ export const WorldGeo = memo(
|
|||||||
const positionCustomTooltip = () => {
|
const positionCustomTooltip = () => {
|
||||||
if (!customTooltipRef.current || !proxyGeoRef.current) return;
|
if (!customTooltipRef.current || !proxyGeoRef.current) return;
|
||||||
// 找到US点
|
// 找到US点
|
||||||
const coords = geoCoordMap["CA"];
|
const coords = geoCoordMap["GL"];
|
||||||
if (!coords) return;
|
if (!coords) return;
|
||||||
try {
|
try {
|
||||||
// 将地理坐标转换为屏幕坐标
|
// 将地理坐标转换为屏幕坐标
|
||||||
const screenCoord = proxyGeoRef.current.convertToPixel(
|
const screenCoord = proxyGeoRef.current.convertToPixel("geo", coords);
|
||||||
"geo",
|
|
||||||
coords
|
|
||||||
);
|
|
||||||
if (
|
if (
|
||||||
screenCoord &&
|
screenCoord &&
|
||||||
Array.isArray(screenCoord) &&
|
Array.isArray(screenCoord) &&
|
||||||
screenCoord.length === 2
|
screenCoord.length === 2
|
||||||
) {
|
) {
|
||||||
// 设置提示框位置
|
// 设置提示框位置
|
||||||
customTooltipRef.current.style.left = `${
|
customTooltipRef.current.style.left = `${screenCoord[0] + 232 + 7}px`;
|
||||||
screenCoord[0] + 232 + 7
|
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`;
|
}px`;
|
||||||
customTooltipRef.current.style.top = `${
|
customTooltip2Ref.current.style.top = `${
|
||||||
screenCoord[1] + 40 - 190
|
screenCoord[1] + 40 - 218
|
||||||
}px`;
|
}px`;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -195,13 +268,7 @@ export const WorldGeo = memo(
|
|||||||
data
|
data
|
||||||
.map((item: any) => {
|
.map((item: any) => {
|
||||||
const countryCode = item.country_code.toUpperCase();
|
const countryCode = item.country_code.toUpperCase();
|
||||||
|
if (!(["RU", "FR"].includes(countryCode) && item.type === "start"))
|
||||||
if (
|
|
||||||
!(
|
|
||||||
["RU", "FR"].includes(countryCode) &&
|
|
||||||
item.type === "start"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return null;
|
return null;
|
||||||
const coords = geoCoordMap[countryCode] as
|
const coords = geoCoordMap[countryCode] as
|
||||||
| [number, number]
|
| [number, number]
|
||||||
@ -238,52 +305,70 @@ export const WorldGeo = memo(
|
|||||||
const solidData: LinesType[] = [["main", []]]; // 使用"main"替代startCountry.country_code
|
const solidData: LinesType[] = [["main", []]]; // 使用"main"替代startCountry.country_code
|
||||||
// 收集需要显示涟漪效果的所有点(包括连线和不连线的)
|
// 收集需要显示涟漪效果的所有点(包括连线和不连线的)
|
||||||
const ripplePoints: any[] = [];
|
const ripplePoints: any[] = [];
|
||||||
|
|
||||||
// 处理主路径数据
|
// 处理主路径数据
|
||||||
for (let i = 0; i < mainToData.length; i++) {
|
for (let i = 0; i < mainToData.length; i++) {
|
||||||
// 如果是最后一个元素,则跳过(因为没有下一个元素作为终点)
|
// 如果是最后一个元素,则跳过(因为没有下一个元素作为终点)
|
||||||
if (i === mainToData.length - 1) continue;
|
if (i === mainToData.length - 1) continue;
|
||||||
const currentItem = mainToData[i];
|
const currentItem = mainToData[i];
|
||||||
const nextItem = mainToData[i + 1];
|
const nextItem = mainToData[i + 1];
|
||||||
// 获取当前国家代码
|
|
||||||
|
// 获取当前国家代码和颜色
|
||||||
const countryCode = currentItem.country_code.toUpperCase();
|
const countryCode = currentItem.country_code.toUpperCase();
|
||||||
|
const color = currentItem.color || "#0ea5e9"; // 获取颜色,如果没有则使用默认颜色
|
||||||
|
|
||||||
// 如果当前项是起点,下一项是终点
|
// 如果当前项是起点,下一项是终点
|
||||||
if (currentItem.type === "start" && nextItem.type === "end") {
|
if (currentItem.type === "start" && nextItem.type === "end") {
|
||||||
const startCode = countryCode;
|
const startCode = countryCode;
|
||||||
const endCode = nextItem.country_code.toUpperCase();
|
const endCode = nextItem.country_code.toUpperCase();
|
||||||
|
|
||||||
// 无论是否连线,都添加点的涟漪效果
|
// 无论是否连线,都添加点的涟漪效果
|
||||||
const startPoint = createCountryRipple(startCode);
|
const startPoint = createCountryRipple(startCode, color);
|
||||||
const endPoint = createCountryRipple(endCode);
|
const endPoint = createCountryRipple(
|
||||||
|
endCode,
|
||||||
|
nextItem.color || color
|
||||||
|
);
|
||||||
if (startPoint) ripplePoints.push(startPoint);
|
if (startPoint) ripplePoints.push(startPoint);
|
||||||
if (endPoint) ripplePoints.push(endPoint);
|
if (endPoint) ripplePoints.push(endPoint);
|
||||||
|
|
||||||
// 检查是否应该绘制连线
|
// 检查是否应该绘制连线
|
||||||
if (currentItem.isLine !== false) {
|
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++;
|
i++;
|
||||||
}
|
}
|
||||||
// 常规情况:当前项到下一项
|
// 常规情况:当前项到下一项
|
||||||
else {
|
else {
|
||||||
const nextCountryCode = nextItem.country_code.toUpperCase();
|
const nextCountryCode = nextItem.country_code.toUpperCase();
|
||||||
|
|
||||||
// 无论是否连线,都添加点的涟漪效果
|
// 无论是否连线,都添加点的涟漪效果
|
||||||
const currentPoint = createCountryRipple(countryCode);
|
const currentPoint = createCountryRipple(countryCode, color);
|
||||||
const nextPoint = createCountryRipple(nextCountryCode);
|
const nextPoint = createCountryRipple(
|
||||||
|
nextCountryCode,
|
||||||
|
nextItem.color || color
|
||||||
|
);
|
||||||
if (currentPoint) ripplePoints.push(currentPoint);
|
if (currentPoint) ripplePoints.push(currentPoint);
|
||||||
if (nextPoint) ripplePoints.push(nextPoint);
|
if (nextPoint) ripplePoints.push(nextPoint);
|
||||||
|
|
||||||
// 检查是否应该绘制连线
|
// 检查是否应该绘制连线
|
||||||
if (currentItem.isLine !== false) {
|
if (currentItem.isLine !== false) {
|
||||||
solidData[0]?.[1].push(
|
const lineItem = getLineItem(countryCode, nextCountryCode);
|
||||||
getLineItem(countryCode, nextCountryCode)
|
// 添加颜色属性
|
||||||
);
|
lineItem[0].color = color;
|
||||||
|
lineItem[1].color = nextItem.color || color;
|
||||||
|
solidData[0]?.[1].push(lineItem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 虚线数据处理(保持原有逻辑)
|
// 虚线数据处理(保持原有逻辑)
|
||||||
const pathList =
|
const otherLineList: any = [];
|
||||||
screenData?.path_list?.filter(
|
|
||||||
(v: any) => v.name !== screenData?.proxy_info?.name
|
|
||||||
) ?? [];
|
|
||||||
const otherLineList = pathList.map(() => {});
|
|
||||||
return {
|
return {
|
||||||
solidData,
|
solidData,
|
||||||
otherLineList,
|
otherLineList,
|
||||||
@ -294,33 +379,32 @@ export const WorldGeo = memo(
|
|||||||
const convertData = (data: LinesDataType[]) => {
|
const convertData = (data: LinesDataType[]) => {
|
||||||
const res = [];
|
const res = [];
|
||||||
const midpoints = [];
|
const midpoints = [];
|
||||||
|
|
||||||
for (let index = 0; index < data.length; index++) {
|
for (let index = 0; index < data.length; index++) {
|
||||||
const dataIndex = data[index];
|
const dataIndex = data[index];
|
||||||
const fromCoord =
|
const fromCoord = geoCoordMap[dataIndex?.[0]?.country_code ?? ""];
|
||||||
geoCoordMap[dataIndex?.[0]?.country_code ?? ""];
|
|
||||||
const toCoord = geoCoordMap[dataIndex?.[1]?.country_code ?? ""];
|
const toCoord = geoCoordMap[dataIndex?.[1]?.country_code ?? ""];
|
||||||
const fromCountry = dataIndex?.[0]?.country_code ?? "";
|
const fromCountry = dataIndex?.[0]?.country_code ?? "";
|
||||||
const toCountry = dataIndex?.[1]?.country_code ?? "";
|
const toCountry = dataIndex?.[1]?.country_code ?? "";
|
||||||
|
|
||||||
if (fromCoord && toCoord) {
|
if (fromCoord && toCoord) {
|
||||||
res.push([fromCoord, toCoord]);
|
res.push({
|
||||||
|
coords: [fromCoord, toCoord],
|
||||||
|
// 添加颜色属性
|
||||||
|
lineStyle: {
|
||||||
|
color: dataIndex?.[0]?.color || "#0ea5e9",
|
||||||
|
},
|
||||||
|
});
|
||||||
// 计算中点,考虑曲线的弧度
|
// 计算中点,考虑曲线的弧度
|
||||||
const curveness = -0.4; // 与飞线弧度相同
|
const curveness = -0.4; // 与飞线弧度相同
|
||||||
const x1 = fromCoord[0];
|
const x1 = fromCoord[0];
|
||||||
const y1 = fromCoord[1];
|
const y1 = fromCoord[1];
|
||||||
const x2 = toCoord[0];
|
const x2 = toCoord[0];
|
||||||
const y2 = toCoord[1];
|
const y2 = toCoord[1];
|
||||||
|
|
||||||
// 计算控制点
|
// 计算控制点
|
||||||
const cpx = (x1 + x2) / 2 - (y2 - y1) * curveness;
|
const cpx = (x1 + x2) / 2 - (y2 - y1) * curveness;
|
||||||
const cpy = (y1 + y2) / 2 - (x1 - x2) * curveness;
|
const cpy = (y1 + y2) / 2 - (x1 - x2) * curveness;
|
||||||
|
|
||||||
// 计算曲线上的中点 (t=0.5 时的贝塞尔曲线点)
|
// 计算曲线上的中点 (t=0.5 时的贝塞尔曲线点)
|
||||||
const midX = x1 * 0.25 + cpx * 0.5 + x2 * 0.25;
|
const midX = x1 * 0.25 + cpx * 0.5 + x2 * 0.25;
|
||||||
const midY = y1 * 0.25 + cpy * 0.5 + y2 * 0.25;
|
const midY = y1 * 0.25 + cpy * 0.5 + y2 * 0.25;
|
||||||
|
|
||||||
midpoints.push({
|
midpoints.push({
|
||||||
id: `line-label-${index}`,
|
id: `line-label-${index}`,
|
||||||
midpoint: [midX, midY],
|
midpoint: [midX, midY],
|
||||||
@ -329,16 +413,15 @@ export const WorldGeo = memo(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新中点引用
|
// 更新中点引用
|
||||||
lineMidpointsRef.current = midpoints;
|
// lineMidpointsRef.current = midpoints;
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
};
|
};
|
||||||
// 创建双层点效果 - 大点
|
// 创建双层点效果 - 大点
|
||||||
const createDualLayerPoint = (
|
const createDualLayerPoint = (
|
||||||
lastExit: LinesItemType,
|
lastExit: LinesItemType,
|
||||||
isMainPath: boolean = true
|
isMainPath: boolean = true,
|
||||||
|
color?: string
|
||||||
) => {
|
) => {
|
||||||
// 创建数据数组,用于两个散点图层
|
// 创建数据数组,用于两个散点图层
|
||||||
const pointData = lastExit
|
const pointData = lastExit
|
||||||
@ -348,6 +431,7 @@ export const WorldGeo = memo(
|
|||||||
value: v.value,
|
value: v.value,
|
||||||
datas: {
|
datas: {
|
||||||
country_code: v.country_code,
|
country_code: v.country_code,
|
||||||
|
color: v.color, // 添加颜色属性
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
@ -355,8 +439,8 @@ export const WorldGeo = memo(
|
|||||||
// 根据是否是主路径设置不同的大小和颜色
|
// 根据是否是主路径设置不同的大小和颜色
|
||||||
const outerSize = isMainPath ? 8 : 4;
|
const outerSize = isMainPath ? 8 : 4;
|
||||||
const innerSize = isMainPath ? 4 : 2;
|
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"; // 白色内层
|
const innerColor = "#FFFFFF"; // 白色内层
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@ -364,7 +448,9 @@ export const WorldGeo = memo(
|
|||||||
type: "effectScatter",
|
type: "effectScatter",
|
||||||
coordinateSystem: "geo",
|
coordinateSystem: "geo",
|
||||||
zlevel: 3,
|
zlevel: 3,
|
||||||
|
itemStyle: {
|
||||||
color: outerColor,
|
color: outerColor,
|
||||||
|
},
|
||||||
symbol: "circle",
|
symbol: "circle",
|
||||||
symbolSize: outerSize,
|
symbolSize: outerSize,
|
||||||
rippleEffect: {
|
rippleEffect: {
|
||||||
@ -417,10 +503,42 @@ export const WorldGeo = memo(
|
|||||||
} as echarts.SeriesOption,
|
} 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 = (
|
const createPathPoints = (
|
||||||
dataItems: LinesDataType[],
|
dataItems: LinesDataType[],
|
||||||
isMainPath: boolean = true
|
isMainPath: boolean = true,
|
||||||
|
color?: string
|
||||||
) => {
|
) => {
|
||||||
// 创建数据数组
|
// 创建数据数组
|
||||||
const pointData = dataItems.map((dataItem: LinesDataType) => {
|
const pointData = dataItems.map((dataItem: LinesDataType) => {
|
||||||
@ -429,14 +547,15 @@ export const WorldGeo = memo(
|
|||||||
value: geoCoordMap[dataItem[0].country_code],
|
value: geoCoordMap[dataItem[0].country_code],
|
||||||
datas: {
|
datas: {
|
||||||
country_code: dataItem[0].country_code,
|
country_code: dataItem[0].country_code,
|
||||||
|
color: dataItem[0].color, // 添加颜色属性
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
// 根据是否是主路径设置不同的大小和颜色
|
// 根据是否是主路径设置不同的大小和颜色
|
||||||
const outerSize = isMainPath ? 8 : 4;
|
const outerSize = isMainPath ? 8 : 4;
|
||||||
const innerSize = isMainPath ? 4 : 2;
|
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"; // 白色内层
|
const innerColor = "#FFFFFF"; // 白色内层
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@ -444,7 +563,9 @@ export const WorldGeo = memo(
|
|||||||
type: "effectScatter",
|
type: "effectScatter",
|
||||||
coordinateSystem: "geo",
|
coordinateSystem: "geo",
|
||||||
zlevel: 3,
|
zlevel: 3,
|
||||||
|
itemStyle: {
|
||||||
color: outerColor,
|
color: outerColor,
|
||||||
|
},
|
||||||
symbol: "circle",
|
symbol: "circle",
|
||||||
symbolSize: outerSize,
|
symbolSize: outerSize,
|
||||||
rippleEffect: {
|
rippleEffect: {
|
||||||
@ -497,13 +618,17 @@ export const WorldGeo = memo(
|
|||||||
};
|
};
|
||||||
// 创建带自定义提示框的涟漪点
|
// 创建带自定义提示框的涟漪点
|
||||||
const createRipplePointsWithTooltip = (ripplePoints: any) => {
|
const createRipplePointsWithTooltip = (ripplePoints: any) => {
|
||||||
// Use selectedApp.color if available, otherwise default to blue
|
console.log(ripplePoints, "asdasdas");
|
||||||
const outerColor = selectedApp?.color || "#0ea5e9"; // Use selectedApp.color with fallback
|
|
||||||
return {
|
return {
|
||||||
type: "effectScatter",
|
type: "effectScatter",
|
||||||
coordinateSystem: "geo",
|
coordinateSystem: "geo",
|
||||||
zlevel: 3,
|
zlevel: 3,
|
||||||
color: outerColor,
|
// 使用函数动态设置每个点的颜色
|
||||||
|
itemStyle: {
|
||||||
|
color: (params: any) => {
|
||||||
|
return params.data.datas?.color || "#0ea5e9"; // 使用点的颜色或默认颜色
|
||||||
|
},
|
||||||
|
},
|
||||||
symbol: "circle",
|
symbol: "circle",
|
||||||
symbolSize: 8,
|
symbolSize: 8,
|
||||||
rippleEffect: {
|
rippleEffect: {
|
||||||
@ -547,6 +672,7 @@ export const WorldGeo = memo(
|
|||||||
value: point.value,
|
value: point.value,
|
||||||
datas: {
|
datas: {
|
||||||
country_code: point.country_code,
|
country_code: point.country_code,
|
||||||
|
color: point.color, // 添加颜色属性
|
||||||
},
|
},
|
||||||
})),
|
})),
|
||||||
} as echarts.SeriesOption;
|
} as echarts.SeriesOption;
|
||||||
@ -574,16 +700,21 @@ export const WorldGeo = memo(
|
|||||||
value: point.value,
|
value: point.value,
|
||||||
datas: {
|
datas: {
|
||||||
country_code: point.country_code,
|
country_code: point.country_code,
|
||||||
|
color: point.color, // 添加颜色属性
|
||||||
},
|
},
|
||||||
})),
|
})),
|
||||||
} as echarts.SeriesOption);
|
} as echarts.SeriesOption);
|
||||||
}
|
}
|
||||||
solidData.forEach((item) => {
|
solidData.forEach((item) => {
|
||||||
|
console.log(item, "item");
|
||||||
// 如果没有连线数据,则跳过
|
// 如果没有连线数据,则跳过
|
||||||
if (item[1].length === 0) {
|
if (item[1].length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const lastExit = item[1]?.[item[1].length - 1]?.[1] ?? null;
|
const lastExit = item[1]?.[item[1].length - 1]?.[1] ?? null;
|
||||||
|
// 获取当前路径的颜色
|
||||||
|
const pathColor = item[1]?.[0]?.[0]?.color || "#0ea5e9"; // 从第一个点获取颜色,如果没有则使用默认颜色
|
||||||
|
|
||||||
// 添加飞行线
|
// 添加飞行线
|
||||||
series.push({
|
series.push({
|
||||||
name: item[0],
|
name: item[0],
|
||||||
@ -604,26 +735,29 @@ export const WorldGeo = memo(
|
|||||||
lineStyle: {
|
lineStyle: {
|
||||||
curveness: -0.4, // 飞线弧度
|
curveness: -0.4, // 飞线弧度
|
||||||
type: "solid", // 飞线类型
|
type: "solid", // 飞线类型
|
||||||
color: selectedApp?.color || "#0ea5e9", // Use selectedApp.color with fallback
|
color: pathColor, // 使用从数据中获取的颜色
|
||||||
width: 1.5, // 飞线宽度
|
width: 1.5, // 飞线宽度
|
||||||
opacity: 0.1,
|
opacity: 0.1,
|
||||||
},
|
},
|
||||||
data: convertData(
|
data: convertData(item[1]) as echarts.LinesSeriesOption["data"],
|
||||||
item[1]
|
|
||||||
) as echarts.LinesSeriesOption["data"],
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 添加路径点的双层效果
|
// 添加路径点的双层效果
|
||||||
const pathPoints = createPathPoints(item[1], true);
|
const pathPoints = createPathPoints(item[1], true, pathColor);
|
||||||
series.push(...pathPoints);
|
series.push(...pathPoints);
|
||||||
|
|
||||||
// 添加出口节点的双层效果
|
// 添加出口节点的双层效果
|
||||||
if (lastExit) {
|
if (lastExit) {
|
||||||
const exitNodes = createDualLayerPoint(lastExit, true);
|
const exitNodes = createDualLayerPoint(lastExit, true, pathColor);
|
||||||
series.push(...exitNodes);
|
series.push(...exitNodes);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
otherLineList.forEach((line: any) => {
|
otherLineList.forEach((line: any) => {
|
||||||
line.forEach((item: any) => {
|
line.forEach((item: any) => {
|
||||||
const lastExit = item[1]?.[item[1].length - 1]?.[1] ?? null;
|
const lastExit = item[1]?.[item[1].length - 1]?.[1] ?? null;
|
||||||
|
// 获取当前路径的颜色
|
||||||
|
const pathColor = item[1]?.[0]?.[0]?.color || "#F0FFA2"; // 从第一个点获取颜色,如果没有则使用默认颜色
|
||||||
|
|
||||||
// 添加虚线
|
// 添加虚线
|
||||||
series.push({
|
series.push({
|
||||||
name: item[0],
|
name: item[0],
|
||||||
@ -636,20 +770,18 @@ export const WorldGeo = memo(
|
|||||||
lineStyle: {
|
lineStyle: {
|
||||||
curveness: -0.4, // 飞线弧度
|
curveness: -0.4, // 飞线弧度
|
||||||
type: [5, 5], // 飞线类型
|
type: [5, 5], // 飞线类型
|
||||||
color: "#F0FFA2", // 飞线颜色
|
color: pathColor, // 使用从数据中获取的颜色
|
||||||
width: 0.5, // 飞线宽度
|
width: 0.5, // 飞线宽度
|
||||||
opacity: 0.6,
|
opacity: 0.6,
|
||||||
},
|
},
|
||||||
data: convertData(
|
data: convertData(item[1]) as echarts.LinesSeriesOption["data"],
|
||||||
item[1]
|
|
||||||
) as echarts.LinesSeriesOption["data"],
|
|
||||||
});
|
});
|
||||||
// 添加路径点的双层效果(次要路径)
|
// 添加路径点的双层效果(次要路径)
|
||||||
const pathPoints = createPathPoints(item[1], false);
|
const pathPoints = createPathPoints(item[1], false, pathColor);
|
||||||
series.push(...pathPoints);
|
series.push(...pathPoints);
|
||||||
// 添加出口节点的双层效果(次要路径)
|
// 添加出口节点的双层效果(次要路径)
|
||||||
if (lastExit) {
|
if (lastExit) {
|
||||||
const exitNodes = createDualLayerPoint(lastExit, false);
|
const exitNodes = createDualLayerPoint(lastExit, false, pathColor);
|
||||||
series.push(...exitNodes);
|
series.push(...exitNodes);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -700,7 +832,6 @@ export const WorldGeo = memo(
|
|||||||
height: 35,
|
height: 35,
|
||||||
width: 8,
|
width: 8,
|
||||||
},
|
},
|
||||||
|
|
||||||
gap2: {
|
gap2: {
|
||||||
height: 35,
|
height: 35,
|
||||||
width: 6,
|
width: 6,
|
||||||
@ -744,14 +875,12 @@ export const WorldGeo = memo(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 创建A点和B点,并添加飞线和标签
|
// 创建A点和B点,并添加飞线和标签
|
||||||
const createSpecialPoints = (series: echarts.SeriesOption[]) => {
|
const createSpecialPoints = (series: echarts.SeriesOption[]) => {
|
||||||
// 定义点A和点B的坐标
|
// 定义点A和点B的坐标
|
||||||
const pointA = [-42.604303, 71.706936];
|
const pointA = [-42.604303, 71.706936];
|
||||||
const pointB = [-106.346771, 56.130366];
|
const pointB = [-106.346771, 56.130366];
|
||||||
const newPointB = [pointB[0] + 14, pointB[1] + 10];
|
const newPointB = [pointB[0] + 14, pointB[1] + 10];
|
||||||
|
|
||||||
// 添加A点 - 带涟漪效果的双层点
|
// 添加A点 - 带涟漪效果的双层点
|
||||||
series.push(
|
series.push(
|
||||||
// 外层带涟漪效果的点
|
// 外层带涟漪效果的点
|
||||||
@ -794,7 +923,6 @@ export const WorldGeo = memo(
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
} as echarts.SeriesOption,
|
} as echarts.SeriesOption,
|
||||||
|
|
||||||
// 内层白色点
|
// 内层白色点
|
||||||
{
|
{
|
||||||
type: "scatter", // 普通scatter,不带特效
|
type: "scatter", // 普通scatter,不带特效
|
||||||
@ -814,7 +942,6 @@ export const WorldGeo = memo(
|
|||||||
],
|
],
|
||||||
} as echarts.SeriesOption
|
} as echarts.SeriesOption
|
||||||
);
|
);
|
||||||
|
|
||||||
// 添加B点 - 大型圆形区域
|
// 添加B点 - 大型圆形区域
|
||||||
series.push({
|
series.push({
|
||||||
type: "scatter",
|
type: "scatter",
|
||||||
@ -849,7 +976,6 @@ export const WorldGeo = memo(
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
} as echarts.SeriesOption);
|
} as echarts.SeriesOption);
|
||||||
|
|
||||||
// 添加A到B的飞线(无特效)
|
// 添加A到B的飞线(无特效)
|
||||||
series.push({
|
series.push({
|
||||||
type: "lines",
|
type: "lines",
|
||||||
@ -870,22 +996,18 @@ export const WorldGeo = memo(
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
} as echarts.SeriesOption);
|
} as echarts.SeriesOption);
|
||||||
|
|
||||||
// 计算飞线中点坐标(考虑曲率)
|
// 计算飞线中点坐标(考虑曲率)
|
||||||
const x1 = pointA[0];
|
const x1 = pointA[0];
|
||||||
const y1 = pointA[1];
|
const y1 = pointA[1];
|
||||||
const x2 = newPointB[0];
|
const x2 = newPointB[0];
|
||||||
const y2 = newPointB[1];
|
const y2 = newPointB[1];
|
||||||
const curveness = -0.4;
|
const curveness = -0.4;
|
||||||
|
|
||||||
// 计算控制点
|
// 计算控制点
|
||||||
const cpx = (x1 + x2) / 2 - (y2 - y1) * curveness;
|
const cpx = (x1 + x2) / 2 - (y2 - y1) * curveness;
|
||||||
const cpy = (y1 + y2) / 2 - (x1 - x2) * curveness;
|
const cpy = (y1 + y2) / 2 - (x1 - x2) * curveness;
|
||||||
|
|
||||||
// 计算曲线上的中点 (t=0.5 时的贝塞尔曲线点)
|
// 计算曲线上的中点 (t=0.5 时的贝塞尔曲线点)
|
||||||
const midX = x1 * 0.25 + cpx * 0.5 + x2 * 0.25;
|
const midX = x1 * 0.25 + cpx * 0.5 + x2 * 0.25;
|
||||||
const midY = y1 * 0.25 + cpy * 0.5 + y2 * 0.25;
|
const midY = y1 * 0.25 + cpy * 0.5 + y2 * 0.25;
|
||||||
|
|
||||||
// 将中点添加到 lineMidpointsRef 中,以便使用 DOM 方式创建标签
|
// 将中点添加到 lineMidpointsRef 中,以便使用 DOM 方式创建标签
|
||||||
lineMidpointsRef.current.push({
|
lineMidpointsRef.current.push({
|
||||||
id: "special-line-label",
|
id: "special-line-label",
|
||||||
@ -893,15 +1015,21 @@ export const WorldGeo = memo(
|
|||||||
fromCountry: "A",
|
fromCountry: "A",
|
||||||
toCountry: "B",
|
toCountry: "B",
|
||||||
});
|
});
|
||||||
|
|
||||||
return series;
|
return series;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getOption = () => {
|
const getOption = () => {
|
||||||
const series: echarts.SeriesOption[] = [];
|
const series: echarts.SeriesOption[] = [];
|
||||||
getLianData(series);
|
getLianData(series);
|
||||||
// getMianLineTipData(series); // 添加主线tip 暂时隐藏
|
// getMianLineTipData(series); // 添加主线tip 暂时隐藏
|
||||||
|
if (tooltipType === "PASS_AUTHENTICATION") {
|
||||||
createSpecialPoints(series); // 添加特殊点和飞线
|
createSpecialPoints(series); // 添加特殊点和飞线
|
||||||
|
if (newHomeProxies[0]?.authenticationPoint) {
|
||||||
|
createRipplePointsFromCoordinates(
|
||||||
|
newHomeProxies[0]?.authenticationPoint || [],
|
||||||
|
series
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const option = {
|
const option = {
|
||||||
backgroundColor: "transparent",
|
backgroundColor: "transparent",
|
||||||
@ -969,8 +1097,7 @@ export const WorldGeo = memo(
|
|||||||
}
|
}
|
||||||
| undefined;
|
| undefined;
|
||||||
}) => {
|
}) => {
|
||||||
if (parameters.data?.name)
|
if (parameters.data?.name) return parameters.data.name;
|
||||||
return parameters.data.name;
|
|
||||||
return parameters.name;
|
return parameters.name;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -979,12 +1106,12 @@ export const WorldGeo = memo(
|
|||||||
};
|
};
|
||||||
return option;
|
return option;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 创建DOM标签
|
// 创建DOM标签
|
||||||
const createDOMLabels = () => {
|
const createDOMLabels = () => {
|
||||||
// 清除现有标签
|
// 清除现有标签
|
||||||
if (labelContainerRef.current) {
|
if (labelContainerRef.current) {
|
||||||
labelContainerRef.current.innerHTML = "";
|
labelContainerRef.current.innerHTML = "";
|
||||||
|
labelsRef.current?.forEach((item) => item?.remove());
|
||||||
labelsRef.current = [];
|
labelsRef.current = [];
|
||||||
} else {
|
} else {
|
||||||
// 创建标签容器
|
// 创建标签容器
|
||||||
@ -998,7 +1125,6 @@ export const WorldGeo = memo(
|
|||||||
container.style.width = "100%";
|
container.style.width = "100%";
|
||||||
container.style.height = "100%";
|
container.style.height = "100%";
|
||||||
container.style.overflow = "hidden";
|
container.style.overflow = "hidden";
|
||||||
|
|
||||||
// 添加到地图容器
|
// 添加到地图容器
|
||||||
const chartDom = document.getElementById("screenGeo");
|
const chartDom = document.getElementById("screenGeo");
|
||||||
if (chartDom) {
|
if (chartDom) {
|
||||||
@ -1007,7 +1133,6 @@ export const WorldGeo = memo(
|
|||||||
labelContainerRef.current = container;
|
labelContainerRef.current = container;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建新标签
|
// 创建新标签
|
||||||
lineMidpointsRef.current.forEach((point, index) => {
|
lineMidpointsRef.current.forEach((point, index) => {
|
||||||
const label = document.createElement("div");
|
const label = document.createElement("div");
|
||||||
@ -1019,7 +1144,6 @@ export const WorldGeo = memo(
|
|||||||
label.style.whiteSpace = "nowrap";
|
label.style.whiteSpace = "nowrap";
|
||||||
label.style.pointerEvents = "none";
|
label.style.pointerEvents = "none";
|
||||||
label.style.zIndex = "1001";
|
label.style.zIndex = "1001";
|
||||||
|
|
||||||
// 特殊线标签(A到B的线)
|
// 特殊线标签(A到B的线)
|
||||||
if (point.id === "special-line-label") {
|
if (point.id === "special-line-label") {
|
||||||
label.style.backgroundColor = "#8B3700";
|
label.style.backgroundColor = "#8B3700";
|
||||||
@ -1040,24 +1164,19 @@ export const WorldGeo = memo(
|
|||||||
label.style.fontWeight = "normal";
|
label.style.fontWeight = "normal";
|
||||||
label.textContent = "SS签名";
|
label.textContent = "SS签名";
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加到容器
|
// 添加到容器
|
||||||
labelContainerRef.current?.appendChild(label);
|
labelContainerRef.current?.appendChild(label);
|
||||||
labelsRef.current.push(label);
|
labelsRef.current.push(label);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 更新标签位置
|
// 更新标签位置
|
||||||
updateLabelPositions();
|
updateLabelPositions();
|
||||||
};
|
};
|
||||||
|
|
||||||
// 更新标签位置
|
// 更新标签位置
|
||||||
const updateLabelPositions = () => {
|
const updateLabelPositions = () => {
|
||||||
if (!proxyGeoRef.current || !labelContainerRef.current) return;
|
if (!proxyGeoRef.current || !labelContainerRef.current) return;
|
||||||
|
|
||||||
lineMidpointsRef.current.forEach((point, index) => {
|
lineMidpointsRef.current.forEach((point, index) => {
|
||||||
const label = labelsRef.current[index];
|
const label = labelsRef.current[index];
|
||||||
if (!label) return;
|
if (!label) return;
|
||||||
|
|
||||||
const pixelPoint = proxyGeoRef.current?.convertToPixel(
|
const pixelPoint = proxyGeoRef.current?.convertToPixel(
|
||||||
"geo",
|
"geo",
|
||||||
point.midpoint
|
point.midpoint
|
||||||
@ -1068,64 +1187,70 @@ export const WorldGeo = memo(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleResize = () => {
|
const handleResize = () => {
|
||||||
proxyGeoRef.current?.resize();
|
proxyGeoRef.current?.resize();
|
||||||
updateLabelPositions();
|
updateLabelPositions();
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
preMainToData.current?.some(
|
preMainToData.current?.some(
|
||||||
(item, index) =>
|
(item, index) => item.country_code !== mainToData[index]?.country_code
|
||||||
item.country_code !== mainToData[index]?.country_code
|
|
||||||
) && proxyGeoRef.current?.clear();
|
) && proxyGeoRef.current?.clear();
|
||||||
preMainToData.current = mainToData;
|
preMainToData.current = mainToData;
|
||||||
const option = getOption();
|
const option = getOption();
|
||||||
proxyGeoRef.current?.setOption(option);
|
proxyGeoRef.current?.setOption(option);
|
||||||
|
|
||||||
// 创建DOM标签
|
// 创建DOM标签
|
||||||
setTimeout(createDOMLabels, 100);
|
setTimeout(createDOMLabels, 100);
|
||||||
}, [screenData, mainToData]);
|
}, [newHomeProxies, mainToData]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const chartDom = document.getElementById("screenGeo");
|
const chartDom = document.getElementById("screenGeo");
|
||||||
proxyGeoRef.current = echarts.init(chartDom);
|
proxyGeoRef.current = echarts.init(chartDom);
|
||||||
echarts.registerMap(
|
echarts.registerMap(
|
||||||
"world",
|
"world",
|
||||||
worldGeoJson as unknown as Parameters<
|
worldGeoJson as unknown as Parameters<typeof echarts.registerMap>[1]
|
||||||
typeof echarts.registerMap
|
|
||||||
>[1]
|
|
||||||
);
|
);
|
||||||
const option = getOption();
|
const option = getOption();
|
||||||
option && proxyGeoRef.current?.setOption(option);
|
option && proxyGeoRef.current?.setOption(option);
|
||||||
|
|
||||||
// 添加地图交互事件监听器
|
// 添加地图交互事件监听器
|
||||||
proxyGeoRef.current?.on("georoam", updateLabelPositions);
|
proxyGeoRef.current?.on("georoam", updateLabelPositions);
|
||||||
|
|
||||||
// 页面resize时触发
|
// 页面resize时触发
|
||||||
window.addEventListener("resize", handleResize);
|
window.addEventListener("resize", handleResize);
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener("resize", handleResize);
|
window.removeEventListener("resize", handleResize);
|
||||||
proxyGeoRef.current?.off("georoam", updateLabelPositions);
|
proxyGeoRef.current?.off("georoam", updateLabelPositions);
|
||||||
|
|
||||||
// 清理DOM标签
|
// 清理DOM标签
|
||||||
if (labelContainerRef.current) {
|
if (labelContainerRef.current) {
|
||||||
labelContainerRef.current.remove();
|
labelContainerRef.current.remove();
|
||||||
labelContainerRef.current = null;
|
labelContainerRef.current = null;
|
||||||
labelsRef.current = [];
|
labelsRef.current = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
proxyGeoRef.current?.dispose();
|
proxyGeoRef.current?.dispose();
|
||||||
proxyGeoRef.current = null;
|
proxyGeoRef.current = null;
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (tooltipType !== "PASS_AUTHENTICATION") {
|
||||||
|
lineMidpointsRef.current = [];
|
||||||
|
}
|
||||||
if (tooltipClosed) {
|
if (tooltipClosed) {
|
||||||
|
if (tooltipType === "NESTED_ENCRYPTION") {
|
||||||
createCustomTooltip();
|
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]);
|
}, [tooltipClosed, tooltipType]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex-1 h-full flex flex-col">
|
<div className="flex-1 h-full flex flex-col">
|
||||||
<div id="screenGeo" className="flex-1"></div>
|
<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;
|
width: 312.221px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.line-img-left{
|
||||||
|
width: 216.86px;
|
||||||
|
margin-top: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
.fill {
|
.fill {
|
||||||
width: 9.165px;
|
width: 9.165px;
|
||||||
height: 9.165px;
|
height: 9.165px;
|
||||||
@ -141,6 +146,17 @@
|
|||||||
top: 77.5px;
|
top: 77.5px;
|
||||||
z-index: 99;
|
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 FacebookActiveSvg from "@/assets/svg/anti-forensics-forwarding/FacebookActive.svg?react";
|
||||||
import { RootState } from "@/store";
|
import { RootState } from "@/store";
|
||||||
|
|
||||||
|
import {
|
||||||
|
TRAFFIC_OBFUSCATION,
|
||||||
|
NESTED_ENCRYPTION,
|
||||||
|
DYNAMIC_ROUTE_GENERATOR,
|
||||||
|
APP_DIVERSION,
|
||||||
|
PASS_AUTHENTICATION,
|
||||||
|
} from "./data/mockData";
|
||||||
|
|
||||||
import "./index.scss";
|
import "./index.scss";
|
||||||
import { color } from "echarts";
|
import { App } from "antd";
|
||||||
|
|
||||||
export const DIALOGTYPE = {
|
export const DIALOGTYPE = {
|
||||||
ADDNode: {
|
ADDNode: {
|
||||||
@ -64,365 +72,66 @@ export const Apps = [
|
|||||||
name: "Netflix",
|
name: "Netflix",
|
||||||
icon: NetflixSvg,
|
icon: NetflixSvg,
|
||||||
activeIcon: NetflixActiveSvg,
|
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",
|
name: "Spotify",
|
||||||
icon: SpotifySvg,
|
icon: SpotifySvg,
|
||||||
activeIcon: SpotifyActiveSvg,
|
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",
|
name: "Instagram",
|
||||||
icon: InstagramSvg,
|
icon: InstagramSvg,
|
||||||
activeIcon: InstagramActiveSvg,
|
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",
|
name: "Telegram",
|
||||||
icon: TelegramSvg,
|
icon: TelegramSvg,
|
||||||
activeIcon: TelegramActiveSvg,
|
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",
|
name: "Google",
|
||||||
icon: GoogleSvg,
|
icon: GoogleSvg,
|
||||||
activeIcon: GoogleActiveSvg,
|
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",
|
name: "Gmail",
|
||||||
icon: GmailSvg,
|
icon: GmailSvg,
|
||||||
activeIcon: GmailActiveSvg,
|
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",
|
name: "Amazon",
|
||||||
icon: AmazonSvg,
|
icon: AmazonSvg,
|
||||||
activeIcon: AmazonActiveSvg,
|
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",
|
name: "Ebay",
|
||||||
icon: EbaySvg,
|
icon: EbaySvg,
|
||||||
activeIcon: EbayActiveSvg,
|
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",
|
name: "AppleNews",
|
||||||
icon: AppleNewsSvg,
|
icon: AppleNewsSvg,
|
||||||
activeIcon: AppleNewsActiveSvg,
|
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",
|
name: "CNN",
|
||||||
icon: CNNSvg,
|
icon: CNNSvg,
|
||||||
activeIcon: CNNActiveSvg,
|
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",
|
name: "Browser",
|
||||||
icon: BrowserSvg,
|
icon: BrowserSvg,
|
||||||
activeIcon: BrowserActiveSvg,
|
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",
|
name: "YouTube",
|
||||||
icon: YouTubeSvg,
|
icon: YouTubeSvg,
|
||||||
activeIcon: YouTubeActiveSvg,
|
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",
|
name: "Facebook",
|
||||||
icon: FacebookSvg,
|
icon: FacebookSvg,
|
||||||
activeIcon: FacebookActiveSvg,
|
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",
|
type: "TRAFFIC_OBFUSCATION",
|
||||||
title: "流量混淆",
|
title: "流量混淆",
|
||||||
},
|
},
|
||||||
|
DYNAMIC_ROUTE_GENERATOR: {
|
||||||
|
type: "DYNAMIC_ROUTE_GENERATOR",
|
||||||
|
title: "动态路由生成",
|
||||||
|
},
|
||||||
|
// 应用分流
|
||||||
|
APP_DIVERSION: {
|
||||||
|
type: "APP_DIVERSION",
|
||||||
|
title: "应用分流",
|
||||||
|
},
|
||||||
|
// 通行认证
|
||||||
|
PASS_AUTHENTICATION: {
|
||||||
|
type: "PASS_AUTHENTICATION",
|
||||||
|
title: "通行认证",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const DecentralizedElasticNetwork = () => {
|
const DecentralizedElasticNetwork = () => {
|
||||||
const { proxy_info, path_list } = useSelector(
|
const { proxy_info, path_list, newHomeProxies } = useSelector(
|
||||||
(state: RootState) => state.web3Reducer
|
(state: RootState) => state.web3Reducer
|
||||||
);
|
);
|
||||||
|
|
||||||
const [tooltipType, setTooltipType] = useState(
|
const [tooltipType, setTooltipType] = useState(
|
||||||
CONST_TOOLTIP_TYPE.NESTED_ENCRYPTION.type
|
CONST_TOOLTIP_TYPE.PASS_AUTHENTICATION.type
|
||||||
);
|
);
|
||||||
const [tooltipClosed, setTooltipClosed] = useState(false);
|
const [tooltipClosed, setTooltipClosed] = useState(false);
|
||||||
|
|
||||||
const [selectedApp, setSelectedApp] = useState<any>(null);
|
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) => {
|
const handleClickApp = (item: any) => {
|
||||||
console.log("item", item);
|
console.log("item", item);
|
||||||
setSelectedApp(item);
|
setSelectedApp(item);
|
||||||
};
|
};
|
||||||
|
|
||||||
const screenData = useMemo(() => {
|
|
||||||
return {
|
useEffect(()=>{
|
||||||
path_list,
|
()=>{
|
||||||
proxy_info,
|
setTooltipClosed(false);
|
||||||
};
|
}
|
||||||
}, [path_list, proxy_info]);
|
},[])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="decentralized w-full h-full flex flex-col relative">
|
<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">
|
<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 (
|
return (
|
||||||
<div
|
<div
|
||||||
key={item.name}
|
key={item.name}
|
||||||
@ -482,7 +239,8 @@ const DecentralizedElasticNetwork = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="mt-2 w-full h-full flex-1">
|
<div className="mt-2 w-full h-full flex-1">
|
||||||
<WorldGeo
|
<WorldGeo
|
||||||
screenData={screenData}
|
currentValue={currentValue}
|
||||||
|
newHomeProxies={newHomeProxies}
|
||||||
selectedApp={selectedApp}
|
selectedApp={selectedApp}
|
||||||
tooltipType={tooltipType}
|
tooltipType={tooltipType}
|
||||||
tooltipClosed={tooltipClosed}
|
tooltipClosed={tooltipClosed}
|
||||||
@ -490,36 +248,47 @@ const DecentralizedElasticNetwork = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</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="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>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className="bt1 cursor-pointer"
|
className="bt1 cursor-pointer"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setTooltipType(
|
setTooltipType(CONST_TOOLTIP_TYPE.NESTED_ENCRYPTION.type);
|
||||||
CONST_TOOLTIP_TYPE.NESTED_ENCRYPTION.type
|
|
||||||
);
|
|
||||||
setTooltipClosed(true);
|
setTooltipClosed(true);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
嵌套加密
|
嵌套加密
|
||||||
</div>
|
</div>
|
||||||
<div className="bt1 cursor-pointer" onClick={() => {}}>
|
<div
|
||||||
|
className="bt1 cursor-pointer"
|
||||||
|
onClick={() => {
|
||||||
|
setTooltipType(CONST_TOOLTIP_TYPE.DYNAMIC_ROUTE_GENERATOR.type);
|
||||||
|
}}
|
||||||
|
>
|
||||||
动态路由生成
|
动态路由生成
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="bt1 cursor-pointer"
|
className="bt1 cursor-pointer"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setTooltipType(
|
setTooltipType(CONST_TOOLTIP_TYPE.TRAFFIC_OBFUSCATION.type);
|
||||||
CONST_TOOLTIP_TYPE.TRAFFIC_OBFUSCATION.type
|
|
||||||
);
|
|
||||||
setTooltipClosed(true);
|
setTooltipClosed(true);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
流量混淆
|
流量混淆
|
||||||
</div>
|
</div>
|
||||||
<div className="bt1 cursor-pointer" onClick={() => {}}>
|
<div
|
||||||
|
className="bt1 cursor-pointer"
|
||||||
|
onClick={() => {
|
||||||
|
setTooltipType(CONST_TOOLTIP_TYPE.APP_DIVERSION.type);
|
||||||
|
}}
|
||||||
|
>
|
||||||
应用分流
|
应用分流
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -57,13 +57,10 @@ export const WorldGeo = memo(
|
|||||||
>([]);
|
>([]);
|
||||||
const labelContainerRef = useRef<HTMLDivElement | null>(null);
|
const labelContainerRef = useRef<HTMLDivElement | null>(null);
|
||||||
const labelsRef = useRef<HTMLDivElement[]>([]);
|
const labelsRef = useRef<HTMLDivElement[]>([]);
|
||||||
|
|
||||||
const mainToData = useMemo(() => {
|
const mainToData = useMemo(() => {
|
||||||
// 使用新的数据结构
|
// 使用新的数据结构
|
||||||
const proxiesList =
|
const proxiesList =
|
||||||
selectedApp && selectedApp?.jumpList
|
selectedApp && selectedApp ? [...newHomeProxies,selectedApp] : newHomeProxies ?? [];
|
||||||
? [selectedApp.jumpList]
|
|
||||||
: newHomeProxies ?? [];
|
|
||||||
// 初始化数据数组 - 不再包含 startCountry
|
// 初始化数据数组 - 不再包含 startCountry
|
||||||
const data: any = [];
|
const data: any = [];
|
||||||
// 遍历代理列表
|
// 遍历代理列表
|
||||||
@ -79,18 +76,21 @@ export const WorldGeo = memo(
|
|||||||
country_code: item.country_code,
|
country_code: item.country_code,
|
||||||
type: "start",
|
type: "start",
|
||||||
isLine: proxyItem.isLine, // 保存连线标志
|
isLine: proxyItem.isLine, // 保存连线标志
|
||||||
|
color: proxyItem.color, // 保存颜色信息
|
||||||
});
|
});
|
||||||
// 添加终点(ingress_country_code)
|
// 添加终点(ingress_country_code)
|
||||||
data.push({
|
data.push({
|
||||||
country_code: item.ingress_country_code,
|
country_code: item.ingress_country_code,
|
||||||
type: "end",
|
type: "end",
|
||||||
isLine: proxyItem.isLine, // 保存连线标志
|
isLine: proxyItem.isLine, // 保存连线标志
|
||||||
|
color: proxyItem.color, // 保存颜色信息
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// 如果没有 ingress_country_code,只添加 country_code
|
// 如果没有 ingress_country_code,只添加 country_code
|
||||||
data.push({
|
data.push({
|
||||||
country_code: item.country_code,
|
country_code: item.country_code,
|
||||||
isLine: proxyItem.isLine, // 保存连线标志
|
isLine: proxyItem.isLine, // 保存连线标志
|
||||||
|
color: proxyItem.color, // 保存颜色信息
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -98,7 +98,6 @@ export const WorldGeo = memo(
|
|||||||
});
|
});
|
||||||
return data;
|
return data;
|
||||||
}, [newHomeProxies, selectedApp]);
|
}, [newHomeProxies, selectedApp]);
|
||||||
|
|
||||||
// 创建自定义提示框DOM元素
|
// 创建自定义提示框DOM元素
|
||||||
const createCustomTooltip = () => {
|
const createCustomTooltip = () => {
|
||||||
// 如果已经存在自定义提示框,则移除它
|
// 如果已经存在自定义提示框,则移除它
|
||||||
@ -114,9 +113,8 @@ export const WorldGeo = memo(
|
|||||||
tooltip.style.backgroundColor = "transparent";
|
tooltip.style.backgroundColor = "transparent";
|
||||||
// 设置提示框内容
|
// 设置提示框内容
|
||||||
const currentTooltipType =
|
const currentTooltipType =
|
||||||
CONST_TOOLTIP_TYPE[
|
CONST_TOOLTIP_TYPE[tooltipType as keyof typeof CONST_TOOLTIP_TYPE] ||
|
||||||
tooltipType as keyof typeof CONST_TOOLTIP_TYPE
|
CONST_TOOLTIP_TYPE.NESTED_ENCRYPTION;
|
||||||
] || CONST_TOOLTIP_TYPE.NESTED_ENCRYPTION;
|
|
||||||
tooltip.innerHTML = `
|
tooltip.innerHTML = `
|
||||||
<div class="tooltip-content">
|
<div class="tooltip-content">
|
||||||
<img class="line-img" src="${getUrl(
|
<img class="line-img" src="${getUrl(
|
||||||
@ -133,19 +131,16 @@ export const WorldGeo = memo(
|
|||||||
)}" alt=""
|
)}" alt=""
|
||||||
style="cursor: pointer; " />
|
style="cursor: pointer; " />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<img class="${
|
<img class="${
|
||||||
CONST_TOOLTIP_TYPE.NESTED_ENCRYPTION.type ===
|
CONST_TOOLTIP_TYPE.NESTED_ENCRYPTION.type ===
|
||||||
currentTooltipType.type
|
currentTooltipType.type
|
||||||
? "encryption-img"
|
? "encryption-img"
|
||||||
: "traffic-obfuscation-img"
|
: "traffic-obfuscation-img"
|
||||||
}" src="${getUrl(
|
}" src="${getUrl(
|
||||||
CONST_TOOLTIP_TYPE.NESTED_ENCRYPTION.type ===
|
CONST_TOOLTIP_TYPE.NESTED_ENCRYPTION.type === currentTooltipType.type
|
||||||
currentTooltipType.type
|
|
||||||
? "image/nested-encryption.png"
|
? "image/nested-encryption.png"
|
||||||
: "image/traffic-obfuscation.png"
|
: "image/traffic-obfuscation.png"
|
||||||
)}" alt="" />
|
)}" alt="" />
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
@ -172,21 +167,88 @@ export const WorldGeo = memo(
|
|||||||
if (!coords) return;
|
if (!coords) return;
|
||||||
try {
|
try {
|
||||||
// 将地理坐标转换为屏幕坐标
|
// 将地理坐标转换为屏幕坐标
|
||||||
const screenCoord = proxyGeoRef.current.convertToPixel(
|
const screenCoord = proxyGeoRef.current.convertToPixel("geo", coords);
|
||||||
"geo",
|
|
||||||
coords
|
|
||||||
);
|
|
||||||
if (
|
if (
|
||||||
screenCoord &&
|
screenCoord &&
|
||||||
Array.isArray(screenCoord) &&
|
Array.isArray(screenCoord) &&
|
||||||
screenCoord.length === 2
|
screenCoord.length === 2
|
||||||
) {
|
) {
|
||||||
// 设置提示框位置
|
// 设置提示框位置
|
||||||
customTooltipRef.current.style.left = `${
|
customTooltipRef.current.style.left = `${screenCoord[0] + 232 + 7}px`;
|
||||||
screenCoord[0] + 232 + 7
|
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`;
|
}px`;
|
||||||
customTooltipRef.current.style.top = `${
|
customTooltip2Ref.current.style.top = `${
|
||||||
screenCoord[1] + 40 - 190
|
screenCoord[1] + 40 - 218
|
||||||
}px`;
|
}px`;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -199,13 +261,7 @@ export const WorldGeo = memo(
|
|||||||
data
|
data
|
||||||
.map((item: any) => {
|
.map((item: any) => {
|
||||||
const countryCode = item.country_code.toUpperCase();
|
const countryCode = item.country_code.toUpperCase();
|
||||||
|
if (!(["RU", "FR"].includes(countryCode) && item.type === "start"))
|
||||||
if (
|
|
||||||
!(
|
|
||||||
["RU", "FR"].includes(countryCode) &&
|
|
||||||
item.type === "start"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return null;
|
return null;
|
||||||
const coords = geoCoordMap[countryCode] as
|
const coords = geoCoordMap[countryCode] as
|
||||||
| [number, number]
|
| [number, number]
|
||||||
@ -242,6 +298,9 @@ export const WorldGeo = memo(
|
|||||||
const solidData: LinesType[] = [["main", []]]; // 使用"main"替代startCountry.country_code
|
const solidData: LinesType[] = [["main", []]]; // 使用"main"替代startCountry.country_code
|
||||||
// 收集需要显示涟漪效果的所有点(包括连线和不连线的)
|
// 收集需要显示涟漪效果的所有点(包括连线和不连线的)
|
||||||
const ripplePoints: any[] = [];
|
const ripplePoints: any[] = [];
|
||||||
|
// 收集每个点的颜色信息
|
||||||
|
const pointColors: Record<string, string> = {};
|
||||||
|
|
||||||
// 处理主路径数据
|
// 处理主路径数据
|
||||||
for (let i = 0; i < mainToData.length; i++) {
|
for (let i = 0; i < mainToData.length; i++) {
|
||||||
// 如果是最后一个元素,则跳过(因为没有下一个元素作为终点)
|
// 如果是最后一个元素,则跳过(因为没有下一个元素作为终点)
|
||||||
@ -250,18 +309,46 @@ export const WorldGeo = memo(
|
|||||||
const nextItem = mainToData[i + 1];
|
const nextItem = mainToData[i + 1];
|
||||||
// 获取当前国家代码
|
// 获取当前国家代码
|
||||||
const countryCode = currentItem.country_code.toUpperCase();
|
const countryCode = currentItem.country_code.toUpperCase();
|
||||||
|
// 获取颜色信息
|
||||||
|
const lineColor = currentItem.color || "#0ea5e9"; // 默认颜色
|
||||||
|
|
||||||
|
// 保存点的颜色信息
|
||||||
|
pointColors[countryCode] = lineColor;
|
||||||
|
|
||||||
// 如果当前项是起点,下一项是终点
|
// 如果当前项是起点,下一项是终点
|
||||||
if (currentItem.type === "start" && nextItem.type === "end") {
|
if (currentItem.type === "start" && nextItem.type === "end") {
|
||||||
const startCode = countryCode;
|
const startCode = countryCode;
|
||||||
const endCode = nextItem.country_code.toUpperCase();
|
const endCode = nextItem.country_code.toUpperCase();
|
||||||
|
|
||||||
|
// 保存终点的颜色信息
|
||||||
|
pointColors[endCode] = lineColor;
|
||||||
|
|
||||||
// 无论是否连线,都添加点的涟漪效果
|
// 无论是否连线,都添加点的涟漪效果
|
||||||
const startPoint = createCountryRipple(startCode);
|
const startPoint = createCountryRipple(startCode);
|
||||||
const endPoint = createCountryRipple(endCode);
|
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) {
|
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++;
|
i++;
|
||||||
@ -269,58 +356,79 @@ export const WorldGeo = memo(
|
|||||||
// 常规情况:当前项到下一项
|
// 常规情况:当前项到下一项
|
||||||
else {
|
else {
|
||||||
const nextCountryCode = nextItem.country_code.toUpperCase();
|
const nextCountryCode = nextItem.country_code.toUpperCase();
|
||||||
|
|
||||||
|
// 保存下一个点的颜色信息
|
||||||
|
pointColors[nextCountryCode] = nextItem.color || lineColor;
|
||||||
|
|
||||||
// 无论是否连线,都添加点的涟漪效果
|
// 无论是否连线,都添加点的涟漪效果
|
||||||
const currentPoint = createCountryRipple(countryCode);
|
const currentPoint = createCountryRipple(countryCode);
|
||||||
const nextPoint = createCountryRipple(nextCountryCode);
|
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) {
|
if (currentItem.isLine !== false) {
|
||||||
solidData[0]?.[1].push(
|
const lineItem = getLineItem(countryCode, nextCountryCode);
|
||||||
getLineItem(countryCode, nextCountryCode)
|
// 添加颜色信息到连线数据
|
||||||
);
|
solidData[0][1].push({
|
||||||
|
...lineItem,
|
||||||
|
color: lineColor // 添加颜色信息
|
||||||
|
} as any);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 虚线数据处理(保持原有逻辑)
|
// 虚线数据处理(保持原有逻辑)
|
||||||
const otherLineList :any = [];
|
const otherLineList: any = [];
|
||||||
return {
|
return {
|
||||||
solidData,
|
solidData,
|
||||||
otherLineList,
|
otherLineList,
|
||||||
ripplePoints,
|
ripplePoints,
|
||||||
|
pointColors
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
// 获取连线经纬度数据
|
// 获取连线经纬度数据
|
||||||
const convertData = (data: LinesDataType[]) => {
|
const convertData = (data: LinesDataType[]) => {
|
||||||
const res = [];
|
const res = [];
|
||||||
const midpoints = [];
|
const midpoints = [];
|
||||||
|
|
||||||
for (let index = 0; index < data.length; index++) {
|
for (let index = 0; index < data.length; index++) {
|
||||||
const dataIndex = data[index];
|
const dataIndex = data[index];
|
||||||
const fromCoord =
|
const fromCoord = geoCoordMap[dataIndex?.[0]?.country_code ?? ""];
|
||||||
geoCoordMap[dataIndex?.[0]?.country_code ?? ""];
|
|
||||||
const toCoord = geoCoordMap[dataIndex?.[1]?.country_code ?? ""];
|
const toCoord = geoCoordMap[dataIndex?.[1]?.country_code ?? ""];
|
||||||
const fromCountry = dataIndex?.[0]?.country_code ?? "";
|
const fromCountry = dataIndex?.[0]?.country_code ?? "";
|
||||||
const toCountry = dataIndex?.[1]?.country_code ?? "";
|
const toCountry = dataIndex?.[1]?.country_code ?? "";
|
||||||
|
const lineColor = (dataIndex as any)?.color || "#0ea5e9"; // 获取线条颜色
|
||||||
|
|
||||||
if (fromCoord && toCoord) {
|
if (fromCoord && toCoord) {
|
||||||
res.push([fromCoord, toCoord]);
|
res.push({
|
||||||
|
coords: [fromCoord, toCoord],
|
||||||
|
lineStyle: {
|
||||||
|
color: lineColor // 使用自定义颜色
|
||||||
|
}
|
||||||
|
});
|
||||||
// 计算中点,考虑曲线的弧度
|
// 计算中点,考虑曲线的弧度
|
||||||
const curveness = -0.4; // 与飞线弧度相同
|
const curveness = -0.4; // 与飞线弧度相同
|
||||||
const x1 = fromCoord[0];
|
const x1 = fromCoord[0];
|
||||||
const y1 = fromCoord[1];
|
const y1 = fromCoord[1];
|
||||||
const x2 = toCoord[0];
|
const x2 = toCoord[0];
|
||||||
const y2 = toCoord[1];
|
const y2 = toCoord[1];
|
||||||
|
|
||||||
// 计算控制点
|
// 计算控制点
|
||||||
const cpx = (x1 + x2) / 2 - (y2 - y1) * curveness;
|
const cpx = (x1 + x2) / 2 - (y2 - y1) * curveness;
|
||||||
const cpy = (y1 + y2) / 2 - (x1 - x2) * curveness;
|
const cpy = (y1 + y2) / 2 - (x1 - x2) * curveness;
|
||||||
|
|
||||||
// 计算曲线上的中点 (t=0.5 时的贝塞尔曲线点)
|
// 计算曲线上的中点 (t=0.5 时的贝塞尔曲线点)
|
||||||
const midX = x1 * 0.25 + cpx * 0.5 + x2 * 0.25;
|
const midX = x1 * 0.25 + cpx * 0.5 + x2 * 0.25;
|
||||||
const midY = y1 * 0.25 + cpy * 0.5 + y2 * 0.25;
|
const midY = y1 * 0.25 + cpy * 0.5 + y2 * 0.25;
|
||||||
|
|
||||||
midpoints.push({
|
midpoints.push({
|
||||||
id: `line-label-${index}`,
|
id: `line-label-${index}`,
|
||||||
midpoint: [midX, midY],
|
midpoint: [midX, midY],
|
||||||
@ -329,16 +437,15 @@ export const WorldGeo = memo(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新中点引用
|
// 更新中点引用
|
||||||
// lineMidpointsRef.current = midpoints;
|
// lineMidpointsRef.current = midpoints;
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
};
|
};
|
||||||
// 创建双层点效果 - 大点
|
// 创建双层点效果 - 大点
|
||||||
const createDualLayerPoint = (
|
const createDualLayerPoint = (
|
||||||
lastExit: LinesItemType,
|
lastExit: LinesItemType,
|
||||||
isMainPath: boolean = true
|
isMainPath: boolean = true,
|
||||||
|
color?: string
|
||||||
) => {
|
) => {
|
||||||
// 创建数据数组,用于两个散点图层
|
// 创建数据数组,用于两个散点图层
|
||||||
const pointData = lastExit
|
const pointData = lastExit
|
||||||
@ -355,8 +462,8 @@ export const WorldGeo = memo(
|
|||||||
// 根据是否是主路径设置不同的大小和颜色
|
// 根据是否是主路径设置不同的大小和颜色
|
||||||
const outerSize = isMainPath ? 8 : 4;
|
const outerSize = isMainPath ? 8 : 4;
|
||||||
const innerSize = isMainPath ? 4 : 2;
|
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"; // 白色内层
|
const innerColor = "#FFFFFF"; // 白色内层
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@ -417,6 +524,37 @@ export const WorldGeo = memo(
|
|||||||
} as echarts.SeriesOption,
|
} 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 = (
|
const createPathPoints = (
|
||||||
dataItems: LinesDataType[],
|
dataItems: LinesDataType[],
|
||||||
@ -424,27 +562,32 @@ export const WorldGeo = memo(
|
|||||||
) => {
|
) => {
|
||||||
// 创建数据数组
|
// 创建数据数组
|
||||||
const pointData = dataItems.map((dataItem: LinesDataType) => {
|
const pointData = dataItems.map((dataItem: LinesDataType) => {
|
||||||
|
const color = (dataItem as any).color || "#0ea5e9"; // 获取颜色信息
|
||||||
return {
|
return {
|
||||||
name: dataItem[0].name,
|
name: dataItem[0].name,
|
||||||
value: geoCoordMap[dataItem[0].country_code],
|
value: geoCoordMap[dataItem[0].country_code],
|
||||||
datas: {
|
datas: {
|
||||||
country_code: dataItem[0].country_code,
|
country_code: dataItem[0].country_code,
|
||||||
|
color: color // 保存颜色信息
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
// 根据是否是主路径设置不同的大小和颜色
|
// 根据是否是主路径设置不同的大小和颜色
|
||||||
const outerSize = isMainPath ? 8 : 4;
|
const outerSize = isMainPath ? 8 : 4;
|
||||||
const innerSize = isMainPath ? 4 : 2;
|
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"; // 白色内层
|
const innerColor = "#FFFFFF"; // 白色内层
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
// 外层蓝色点,带涟漪效果
|
// 外层彩色点,带涟漪效果
|
||||||
type: "effectScatter",
|
type: "effectScatter",
|
||||||
coordinateSystem: "geo",
|
coordinateSystem: "geo",
|
||||||
zlevel: 3,
|
zlevel: 3,
|
||||||
color: outerColor,
|
// 使用回调函数根据数据项设置颜色
|
||||||
|
itemStyle: {
|
||||||
|
color: function(params: any) {
|
||||||
|
return params.data.datas.color || "#0ea5e9";
|
||||||
|
}
|
||||||
|
},
|
||||||
symbol: "circle",
|
symbol: "circle",
|
||||||
symbolSize: outerSize,
|
symbolSize: outerSize,
|
||||||
rippleEffect: {
|
rippleEffect: {
|
||||||
@ -460,9 +603,6 @@ export const WorldGeo = memo(
|
|||||||
show: false,
|
show: false,
|
||||||
trigger: "item",
|
trigger: "item",
|
||||||
formatter: (params: any) => {
|
formatter: (params: any) => {
|
||||||
// const countryCode = params.data.datas.country_code;
|
|
||||||
// const countryName = params.data.name;
|
|
||||||
// 创建自定义HTML提示框
|
|
||||||
return `
|
return `
|
||||||
<div class="tip-box">
|
<div class="tip-box">
|
||||||
<img class="close-icon" src="${getUrl(
|
<img class="close-icon" src="${getUrl(
|
||||||
@ -497,13 +637,16 @@ export const WorldGeo = memo(
|
|||||||
};
|
};
|
||||||
// 创建带自定义提示框的涟漪点
|
// 创建带自定义提示框的涟漪点
|
||||||
const createRipplePointsWithTooltip = (ripplePoints: any) => {
|
const createRipplePointsWithTooltip = (ripplePoints: any) => {
|
||||||
// Use selectedApp.color if available, otherwise default to blue
|
|
||||||
const outerColor = selectedApp?.color || "#0ea5e9"; // Use selectedApp.color with fallback
|
|
||||||
return {
|
return {
|
||||||
type: "effectScatter",
|
type: "effectScatter",
|
||||||
coordinateSystem: "geo",
|
coordinateSystem: "geo",
|
||||||
zlevel: 3,
|
zlevel: 3,
|
||||||
color: outerColor,
|
// 使用回调函数根据数据项设置颜色
|
||||||
|
itemStyle: {
|
||||||
|
color: function(params: any) {
|
||||||
|
return params.data.datas.color || "#0ea5e9";
|
||||||
|
}
|
||||||
|
},
|
||||||
symbol: "circle",
|
symbol: "circle",
|
||||||
symbolSize: 8,
|
symbolSize: 8,
|
||||||
rippleEffect: {
|
rippleEffect: {
|
||||||
@ -524,9 +667,6 @@ export const WorldGeo = memo(
|
|||||||
show: false,
|
show: false,
|
||||||
trigger: "item",
|
trigger: "item",
|
||||||
formatter: (params: any) => {
|
formatter: (params: any) => {
|
||||||
// const countryCode = params.data.datas.country_code;
|
|
||||||
// const countryName = params.data.name;
|
|
||||||
// 创建自定义HTML提示框
|
|
||||||
return `
|
return `
|
||||||
<div class="tip-box">
|
<div class="tip-box">
|
||||||
<img class="close-icon" src="${getUrl(
|
<img class="close-icon" src="${getUrl(
|
||||||
@ -547,6 +687,7 @@ export const WorldGeo = memo(
|
|||||||
value: point.value,
|
value: point.value,
|
||||||
datas: {
|
datas: {
|
||||||
country_code: point.country_code,
|
country_code: point.country_code,
|
||||||
|
color: point.color || "#0ea5e9" // 保存颜色信息
|
||||||
},
|
},
|
||||||
})),
|
})),
|
||||||
} as echarts.SeriesOption;
|
} as echarts.SeriesOption;
|
||||||
@ -556,13 +697,13 @@ export const WorldGeo = memo(
|
|||||||
const { solidData, otherLineList, ripplePoints } = getLine();
|
const { solidData, otherLineList, ripplePoints } = getLine();
|
||||||
// 如果有需要显示涟漪效果的点,添加它们
|
// 如果有需要显示涟漪效果的点,添加它们
|
||||||
if (ripplePoints.length > 0) {
|
if (ripplePoints.length > 0) {
|
||||||
// 添加带自定义提示框的外层蓝色点
|
// 添加带自定义提示框的外层彩色点
|
||||||
series.push(createRipplePointsWithTooltip(ripplePoints));
|
series.push(createRipplePointsWithTooltip(ripplePoints));
|
||||||
// 添加内层白色点,不带涟漪效果
|
// 添加内层白色点,不带涟漪效果
|
||||||
series.push({
|
series.push({
|
||||||
type: "scatter", // 使用普通scatter,不带特效
|
type: "scatter", // 使用普通scatter,不带特效
|
||||||
coordinateSystem: "geo",
|
coordinateSystem: "geo",
|
||||||
zlevel: 4, // 确保在蓝色点上方
|
zlevel: 4, // 确保在彩色点上方
|
||||||
color: "#FFFFFF", // 白色内层
|
color: "#FFFFFF", // 白色内层
|
||||||
symbol: "circle",
|
symbol: "circle",
|
||||||
symbolSize: 4,
|
symbolSize: 4,
|
||||||
@ -584,6 +725,8 @@ export const WorldGeo = memo(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const lastExit = item[1]?.[item[1].length - 1]?.[1] ?? null;
|
const lastExit = item[1]?.[item[1].length - 1]?.[1] ?? null;
|
||||||
|
const lastExitColor = (item[1]?.[item[1].length - 1] as any)?.color || "#0ea5e9";
|
||||||
|
|
||||||
// 添加飞行线
|
// 添加飞行线
|
||||||
series.push({
|
series.push({
|
||||||
name: item[0],
|
name: item[0],
|
||||||
@ -604,26 +747,25 @@ export const WorldGeo = memo(
|
|||||||
lineStyle: {
|
lineStyle: {
|
||||||
curveness: -0.4, // 飞线弧度
|
curveness: -0.4, // 飞线弧度
|
||||||
type: "solid", // 飞线类型
|
type: "solid", // 飞线类型
|
||||||
color: selectedApp?.color || "#0ea5e9", // Use selectedApp.color with fallback
|
|
||||||
width: 1.5, // 飞线宽度
|
width: 1.5, // 飞线宽度
|
||||||
opacity: 0.1,
|
opacity: 0.1,
|
||||||
},
|
},
|
||||||
data: convertData(
|
data: convertData(item[1]) as echarts.LinesSeriesOption["data"],
|
||||||
item[1]
|
|
||||||
) as echarts.LinesSeriesOption["data"],
|
|
||||||
});
|
});
|
||||||
// 添加路径点的双层效果
|
// 添加路径点的双层效果
|
||||||
const pathPoints = createPathPoints(item[1], true);
|
const pathPoints = createPathPoints(item[1], true);
|
||||||
series.push(...pathPoints);
|
series.push(...pathPoints);
|
||||||
// 添加出口节点的双层效果
|
// 添加出口节点的双层效果
|
||||||
if (lastExit) {
|
if (lastExit) {
|
||||||
const exitNodes = createDualLayerPoint(lastExit, true);
|
const exitNodes = createDualLayerPoint(lastExit, true, lastExitColor);
|
||||||
series.push(...exitNodes);
|
series.push(...exitNodes);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
otherLineList.forEach((line: any) => {
|
otherLineList.forEach((line: any) => {
|
||||||
line.forEach((item: any) => {
|
line.forEach((item: any) => {
|
||||||
const lastExit = item[1]?.[item[1].length - 1]?.[1] ?? null;
|
const lastExit = item[1]?.[item[1].length - 1]?.[1] ?? null;
|
||||||
|
const lastExitColor = (item[1]?.[item[1].length - 1] as any)?.color || "#0ea5e9";
|
||||||
|
|
||||||
// 添加虚线
|
// 添加虚线
|
||||||
series.push({
|
series.push({
|
||||||
name: item[0],
|
name: item[0],
|
||||||
@ -640,16 +782,14 @@ export const WorldGeo = memo(
|
|||||||
width: 0.5, // 飞线宽度
|
width: 0.5, // 飞线宽度
|
||||||
opacity: 0.6,
|
opacity: 0.6,
|
||||||
},
|
},
|
||||||
data: convertData(
|
data: convertData(item[1]) as echarts.LinesSeriesOption["data"],
|
||||||
item[1]
|
|
||||||
) as echarts.LinesSeriesOption["data"],
|
|
||||||
});
|
});
|
||||||
// 添加路径点的双层效果(次要路径)
|
// 添加路径点的双层效果(次要路径)
|
||||||
const pathPoints = createPathPoints(item[1], false);
|
const pathPoints = createPathPoints(item[1], false);
|
||||||
series.push(...pathPoints);
|
series.push(...pathPoints);
|
||||||
// 添加出口节点的双层效果(次要路径)
|
// 添加出口节点的双层效果(次要路径)
|
||||||
if (lastExit) {
|
if (lastExit) {
|
||||||
const exitNodes = createDualLayerPoint(lastExit, false);
|
const exitNodes = createDualLayerPoint(lastExit, false, lastExitColor);
|
||||||
series.push(...exitNodes);
|
series.push(...exitNodes);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -687,20 +827,10 @@ export const WorldGeo = memo(
|
|||||||
}|${name}}{gap3|}{right|}`;
|
}|${name}}{gap3|}{right|}`;
|
||||||
},
|
},
|
||||||
rich: {
|
rich: {
|
||||||
// left: {
|
|
||||||
// color: "transparent",
|
|
||||||
// height: 35,
|
|
||||||
// width: 8,
|
|
||||||
// align: "center",
|
|
||||||
// backgroundColor: {
|
|
||||||
// image: `data:image/svg+xml;charset=utf-8;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMSIgaGVpZ2h0PSI1OCIgdmlld0JveD0iMCAwIDExIDU4IiBmaWxsPSJub25lIj4KPHBhdGggZD0iTTExIDU2LjA4ODRIMVY0Ni4wODg0IiBzdHJva2U9IiNGRkZERDMiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIi8+CjxwYXRoIGQ9Ik0xIDExVjFIMTEiIHN0cm9rZT0iI0ZGRkREMyIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiLz4KPHBhdGggZD0iTTguNzI5NDkgMTlWMzkiIHN0cm9rZT0iI0ZGRkREMyIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiLz4KPC9zdmc+`, // 动态生成国旗图标
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
gap1: {
|
gap1: {
|
||||||
height: 35,
|
height: 35,
|
||||||
width: 8,
|
width: 8,
|
||||||
},
|
},
|
||||||
|
|
||||||
gap2: {
|
gap2: {
|
||||||
height: 35,
|
height: 35,
|
||||||
width: 6,
|
width: 6,
|
||||||
@ -727,15 +857,6 @@ export const WorldGeo = memo(
|
|||||||
height: 35,
|
height: 35,
|
||||||
width: 8,
|
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",
|
backgroundColor: "transparent",
|
||||||
},
|
},
|
||||||
@ -744,14 +865,12 @@ export const WorldGeo = memo(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 创建A点和B点,并添加飞线和标签
|
// 创建A点和B点,并添加飞线和标签
|
||||||
const createSpecialPoints = (series: echarts.SeriesOption[]) => {
|
const createSpecialPoints = (series: echarts.SeriesOption[]) => {
|
||||||
// 定义点A和点B的坐标
|
// 定义点A和点B的坐标
|
||||||
const pointA = [-42.604303, 71.706936];
|
const pointA = [-42.604303, 71.706936];
|
||||||
const pointB = [-106.346771, 56.130366];
|
const pointB = [-106.346771, 56.130366];
|
||||||
const newPointB = [pointB[0] + 14, pointB[1] + 10];
|
const newPointB = [pointB[0] + 14, pointB[1] + 10];
|
||||||
|
|
||||||
// 添加A点 - 带涟漪效果的双层点
|
// 添加A点 - 带涟漪效果的双层点
|
||||||
series.push(
|
series.push(
|
||||||
// 外层带涟漪效果的点
|
// 外层带涟漪效果的点
|
||||||
@ -794,7 +913,6 @@ export const WorldGeo = memo(
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
} as echarts.SeriesOption,
|
} as echarts.SeriesOption,
|
||||||
|
|
||||||
// 内层白色点
|
// 内层白色点
|
||||||
{
|
{
|
||||||
type: "scatter", // 普通scatter,不带特效
|
type: "scatter", // 普通scatter,不带特效
|
||||||
@ -814,7 +932,6 @@ export const WorldGeo = memo(
|
|||||||
],
|
],
|
||||||
} as echarts.SeriesOption
|
} as echarts.SeriesOption
|
||||||
);
|
);
|
||||||
|
|
||||||
// 添加B点 - 大型圆形区域
|
// 添加B点 - 大型圆形区域
|
||||||
series.push({
|
series.push({
|
||||||
type: "scatter",
|
type: "scatter",
|
||||||
@ -849,7 +966,6 @@ export const WorldGeo = memo(
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
} as echarts.SeriesOption);
|
} as echarts.SeriesOption);
|
||||||
|
|
||||||
// 添加A到B的飞线(无特效)
|
// 添加A到B的飞线(无特效)
|
||||||
series.push({
|
series.push({
|
||||||
type: "lines",
|
type: "lines",
|
||||||
@ -870,22 +986,18 @@ export const WorldGeo = memo(
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
} as echarts.SeriesOption);
|
} as echarts.SeriesOption);
|
||||||
|
|
||||||
// 计算飞线中点坐标(考虑曲率)
|
// 计算飞线中点坐标(考虑曲率)
|
||||||
const x1 = pointA[0];
|
const x1 = pointA[0];
|
||||||
const y1 = pointA[1];
|
const y1 = pointA[1];
|
||||||
const x2 = newPointB[0];
|
const x2 = newPointB[0];
|
||||||
const y2 = newPointB[1];
|
const y2 = newPointB[1];
|
||||||
const curveness = -0.4;
|
const curveness = -0.4;
|
||||||
|
|
||||||
// 计算控制点
|
// 计算控制点
|
||||||
const cpx = (x1 + x2) / 2 - (y2 - y1) * curveness;
|
const cpx = (x1 + x2) / 2 - (y2 - y1) * curveness;
|
||||||
const cpy = (y1 + y2) / 2 - (x1 - x2) * curveness;
|
const cpy = (y1 + y2) / 2 - (x1 - x2) * curveness;
|
||||||
|
|
||||||
// 计算曲线上的中点 (t=0.5 时的贝塞尔曲线点)
|
// 计算曲线上的中点 (t=0.5 时的贝塞尔曲线点)
|
||||||
const midX = x1 * 0.25 + cpx * 0.5 + x2 * 0.25;
|
const midX = x1 * 0.25 + cpx * 0.5 + x2 * 0.25;
|
||||||
const midY = y1 * 0.25 + cpy * 0.5 + y2 * 0.25;
|
const midY = y1 * 0.25 + cpy * 0.5 + y2 * 0.25;
|
||||||
|
|
||||||
// 将中点添加到 lineMidpointsRef 中,以便使用 DOM 方式创建标签
|
// 将中点添加到 lineMidpointsRef 中,以便使用 DOM 方式创建标签
|
||||||
lineMidpointsRef.current.push({
|
lineMidpointsRef.current.push({
|
||||||
id: "special-line-label",
|
id: "special-line-label",
|
||||||
@ -893,16 +1005,19 @@ export const WorldGeo = memo(
|
|||||||
fromCountry: "A",
|
fromCountry: "A",
|
||||||
toCountry: "B",
|
toCountry: "B",
|
||||||
});
|
});
|
||||||
|
|
||||||
return series;
|
return series;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getOption = () => {
|
const getOption = () => {
|
||||||
const series: echarts.SeriesOption[] = [];
|
const series: echarts.SeriesOption[] = [];
|
||||||
getLianData(series);
|
getLianData(series);
|
||||||
// getMianLineTipData(series); // 添加主线tip 暂时隐藏
|
// getMianLineTipData(series); // 添加主线tip 暂时隐藏
|
||||||
createSpecialPoints(series); // 添加特殊点和飞线
|
createSpecialPoints(series); // 添加特殊点和飞线
|
||||||
|
if (newHomeProxies[0]?.authenticationPoint) {
|
||||||
|
createRipplePointsFromCoordinates(
|
||||||
|
newHomeProxies[0]?.authenticationPoint || [],
|
||||||
|
series
|
||||||
|
);
|
||||||
|
}
|
||||||
const option = {
|
const option = {
|
||||||
backgroundColor: "transparent",
|
backgroundColor: "transparent",
|
||||||
// 全局提示框配置
|
// 全局提示框配置
|
||||||
@ -969,8 +1084,7 @@ export const WorldGeo = memo(
|
|||||||
}
|
}
|
||||||
| undefined;
|
| undefined;
|
||||||
}) => {
|
}) => {
|
||||||
if (parameters.data?.name)
|
if (parameters.data?.name) return parameters.data.name;
|
||||||
return parameters.data.name;
|
|
||||||
return parameters.name;
|
return parameters.name;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -979,7 +1093,6 @@ export const WorldGeo = memo(
|
|||||||
};
|
};
|
||||||
return option;
|
return option;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 创建DOM标签
|
// 创建DOM标签
|
||||||
const createDOMLabels = () => {
|
const createDOMLabels = () => {
|
||||||
// 清除现有标签
|
// 清除现有标签
|
||||||
@ -998,7 +1111,6 @@ export const WorldGeo = memo(
|
|||||||
container.style.width = "100%";
|
container.style.width = "100%";
|
||||||
container.style.height = "100%";
|
container.style.height = "100%";
|
||||||
container.style.overflow = "hidden";
|
container.style.overflow = "hidden";
|
||||||
|
|
||||||
// 添加到地图容器
|
// 添加到地图容器
|
||||||
const chartDom = document.getElementById("screenGeo");
|
const chartDom = document.getElementById("screenGeo");
|
||||||
if (chartDom) {
|
if (chartDom) {
|
||||||
@ -1007,7 +1119,6 @@ export const WorldGeo = memo(
|
|||||||
labelContainerRef.current = container;
|
labelContainerRef.current = container;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建新标签
|
// 创建新标签
|
||||||
lineMidpointsRef.current.forEach((point, index) => {
|
lineMidpointsRef.current.forEach((point, index) => {
|
||||||
const label = document.createElement("div");
|
const label = document.createElement("div");
|
||||||
@ -1019,7 +1130,6 @@ export const WorldGeo = memo(
|
|||||||
label.style.whiteSpace = "nowrap";
|
label.style.whiteSpace = "nowrap";
|
||||||
label.style.pointerEvents = "none";
|
label.style.pointerEvents = "none";
|
||||||
label.style.zIndex = "1001";
|
label.style.zIndex = "1001";
|
||||||
|
|
||||||
// 特殊线标签(A到B的线)
|
// 特殊线标签(A到B的线)
|
||||||
if (point.id === "special-line-label") {
|
if (point.id === "special-line-label") {
|
||||||
label.style.backgroundColor = "#8B3700";
|
label.style.backgroundColor = "#8B3700";
|
||||||
@ -1040,24 +1150,19 @@ export const WorldGeo = memo(
|
|||||||
label.style.fontWeight = "normal";
|
label.style.fontWeight = "normal";
|
||||||
label.textContent = "SS签名";
|
label.textContent = "SS签名";
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加到容器
|
// 添加到容器
|
||||||
labelContainerRef.current?.appendChild(label);
|
labelContainerRef.current?.appendChild(label);
|
||||||
labelsRef.current.push(label);
|
labelsRef.current.push(label);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 更新标签位置
|
// 更新标签位置
|
||||||
updateLabelPositions();
|
updateLabelPositions();
|
||||||
};
|
};
|
||||||
|
|
||||||
// 更新标签位置
|
// 更新标签位置
|
||||||
const updateLabelPositions = () => {
|
const updateLabelPositions = () => {
|
||||||
if (!proxyGeoRef.current || !labelContainerRef.current) return;
|
if (!proxyGeoRef.current || !labelContainerRef.current) return;
|
||||||
|
|
||||||
lineMidpointsRef.current.forEach((point, index) => {
|
lineMidpointsRef.current.forEach((point, index) => {
|
||||||
const label = labelsRef.current[index];
|
const label = labelsRef.current[index];
|
||||||
if (!label) return;
|
if (!label) return;
|
||||||
|
|
||||||
const pixelPoint = proxyGeoRef.current?.convertToPixel(
|
const pixelPoint = proxyGeoRef.current?.convertToPixel(
|
||||||
"geo",
|
"geo",
|
||||||
point.midpoint
|
point.midpoint
|
||||||
@ -1068,64 +1173,64 @@ export const WorldGeo = memo(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleResize = () => {
|
const handleResize = () => {
|
||||||
proxyGeoRef.current?.resize();
|
proxyGeoRef.current?.resize();
|
||||||
updateLabelPositions();
|
updateLabelPositions();
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
preMainToData.current?.some(
|
preMainToData.current?.some(
|
||||||
(item, index) =>
|
(item, index) => item.country_code !== mainToData[index]?.country_code
|
||||||
item.country_code !== mainToData[index]?.country_code
|
|
||||||
) && proxyGeoRef.current?.clear();
|
) && proxyGeoRef.current?.clear();
|
||||||
preMainToData.current = mainToData;
|
preMainToData.current = mainToData;
|
||||||
const option = getOption();
|
const option = getOption();
|
||||||
proxyGeoRef.current?.setOption(option);
|
proxyGeoRef.current?.setOption(option);
|
||||||
|
|
||||||
// 创建DOM标签
|
// 创建DOM标签
|
||||||
setTimeout(createDOMLabels, 100);
|
setTimeout(createDOMLabels, 100);
|
||||||
}, [newHomeProxies, mainToData]);
|
}, [newHomeProxies, mainToData]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const chartDom = document.getElementById("screenGeo");
|
const chartDom = document.getElementById("screenGeo");
|
||||||
proxyGeoRef.current = echarts.init(chartDom);
|
proxyGeoRef.current = echarts.init(chartDom);
|
||||||
echarts.registerMap(
|
echarts.registerMap(
|
||||||
"world",
|
"world",
|
||||||
worldGeoJson as unknown as Parameters<
|
worldGeoJson as unknown as Parameters<typeof echarts.registerMap>[1]
|
||||||
typeof echarts.registerMap
|
|
||||||
>[1]
|
|
||||||
);
|
);
|
||||||
const option = getOption();
|
const option = getOption();
|
||||||
option && proxyGeoRef.current?.setOption(option);
|
option && proxyGeoRef.current?.setOption(option);
|
||||||
|
|
||||||
// 添加地图交互事件监听器
|
// 添加地图交互事件监听器
|
||||||
proxyGeoRef.current?.on("georoam", updateLabelPositions);
|
proxyGeoRef.current?.on("georoam", updateLabelPositions);
|
||||||
|
|
||||||
// 页面resize时触发
|
// 页面resize时触发
|
||||||
window.addEventListener("resize", handleResize);
|
window.addEventListener("resize", handleResize);
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener("resize", handleResize);
|
window.removeEventListener("resize", handleResize);
|
||||||
proxyGeoRef.current?.off("georoam", updateLabelPositions);
|
proxyGeoRef.current?.off("georoam", updateLabelPositions);
|
||||||
|
|
||||||
// 清理DOM标签
|
// 清理DOM标签
|
||||||
if (labelContainerRef.current) {
|
if (labelContainerRef.current) {
|
||||||
labelContainerRef.current.remove();
|
labelContainerRef.current.remove();
|
||||||
labelContainerRef.current = null;
|
labelContainerRef.current = null;
|
||||||
labelsRef.current = [];
|
labelsRef.current = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
proxyGeoRef.current?.dispose();
|
proxyGeoRef.current?.dispose();
|
||||||
proxyGeoRef.current = null;
|
proxyGeoRef.current = null;
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
console.log(tooltipClosed,'tooltipClosedtooltipClosed')
|
||||||
if (tooltipClosed) {
|
if (tooltipClosed) {
|
||||||
createCustomTooltip();
|
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]);
|
}, [tooltipClosed, tooltipType]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex-1 h-full flex flex-col">
|
<div className="flex-1 h-full flex flex-col">
|
||||||
<div id="screenGeo" className="flex-1"></div>
|
<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 OpenProxyPng from "@/assets/image/home/open-proxy.png";
|
||||||
import web3BoxGif from "@/assets/gif/web3-box-bg.gif";
|
import web3BoxGif from "@/assets/gif/web3-box-bg.gif";
|
||||||
import VectorSlideSvg from "@/assets/svg/home/vector-solide.svg?react";
|
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 { Apps, CONST_TOOLTIP_TYPE } from "@/pages/anti-forensics-forwarding";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
@ -20,13 +18,13 @@ import {
|
|||||||
setProxyInfoProxies,
|
setProxyInfoProxies,
|
||||||
setProxiesList1,
|
setProxiesList1,
|
||||||
setProxiesList2,
|
setProxiesList2,
|
||||||
setProxiesLine,
|
|
||||||
} from "@/store/web3Slice";
|
} from "@/store/web3Slice";
|
||||||
import type { AppDispatch, RootState } from "@/store";
|
import type { AppDispatch, RootState } from "@/store";
|
||||||
import "./index.scss";
|
import "./index.scss";
|
||||||
import { DialogConfig, FormAlertDialog } from "./components/FormAlertDialog";
|
import { DialogConfig, FormAlertDialog } from "./components/FormAlertDialog";
|
||||||
import { ClearNodeDialog } from "./components/ClearNodeDialog";
|
import { ClearNodeDialog } from "./components/ClearNodeDialog";
|
||||||
import { blockChainApi } from "@/api/block";
|
import { blockChainApi } from "@/api/block";
|
||||||
|
import { APP_DIVERSION } from "../anti-forensics-forwarding/data/mockData";
|
||||||
|
|
||||||
export const DIALOGTYPE = {
|
export const DIALOGTYPE = {
|
||||||
ADDNode: {
|
ADDNode: {
|
||||||
@ -71,12 +69,24 @@ const NewHome = () => {
|
|||||||
|
|
||||||
const [blockChain, setBlockChain] = useState<any>(null);
|
const [blockChain, setBlockChain] = useState<any>(null);
|
||||||
|
|
||||||
const [tooltipClosed, setTooltipClosed] = useState(false);
|
const [tooltipClosed, setTooltipClosed] = useState(true);
|
||||||
|
|
||||||
const [tooltipType, setTooltipType] = useState(
|
const [tooltipType, setTooltipType] = useState(
|
||||||
CONST_TOOLTIP_TYPE.NESTED_ENCRYPTION.type
|
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(() => {
|
const newWeb3List = useMemo(() => {
|
||||||
// 展示最新的6个节点
|
// 展示最新的6个节点
|
||||||
return web3List.slice(-6);
|
return web3List.slice(-6);
|
||||||
@ -102,7 +112,6 @@ const NewHome = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleClickApp = (item: any) => {
|
const handleClickApp = (item: any) => {
|
||||||
console.log("item", item);
|
|
||||||
setSelectedApp(item);
|
setSelectedApp(item);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -178,12 +187,14 @@ const NewHome = () => {
|
|||||||
console.log("res",res)
|
console.log("res",res)
|
||||||
setBlockChain(res)
|
setBlockChain(res)
|
||||||
})
|
})
|
||||||
|
|
||||||
},[])
|
},[])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="decentralized w-full h-full flex flex-col relative">
|
<div className="decentralized w-full h-full flex flex-col relative">
|
||||||
<div className="box"></div>
|
<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">
|
<div className="w-full flex items-center justify-center relative">
|
||||||
{/* <img
|
{/* <img
|
||||||
className="w-[1693px] h-[271px] absolute top-[160px] left-[50%] translate-x-[-50%] z-10"
|
className="w-[1693px] h-[271px] absolute top-[160px] left-[50%] translate-x-[-50%] z-10"
|
||||||
@ -307,7 +318,7 @@ const NewHome = () => {
|
|||||||
</div>
|
</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">
|
<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="" />
|
<img src={OpenProxyPng} className="w-[193px] h-[90px] cursor-pointer" alt="" />
|
||||||
<div
|
{/* <div
|
||||||
className="bt1 cursor-pointer"
|
className="bt1 cursor-pointer"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setTooltipType(
|
setTooltipType(
|
||||||
@ -328,8 +339,8 @@ const NewHome = () => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
流量混淆
|
流量混淆
|
||||||
</div>
|
</div> */}
|
||||||
{Apps.map((item) => {
|
{appDiversion.map((item) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={item.name}
|
key={item.name}
|
||||||
|
|||||||
@ -20,7 +20,7 @@ export interface Iweb3 {
|
|||||||
interface Iweb3Slice {
|
interface Iweb3Slice {
|
||||||
web3List: Iweb3[]; // 代表状态一
|
web3List: Iweb3[]; // 代表状态一
|
||||||
web3List2: Iweb3[]; // 代表状态二
|
web3List2: Iweb3[]; // 代表状态二
|
||||||
newHomeProxies:any[];
|
newHomeProxies: any[];
|
||||||
path_list: any;
|
path_list: any;
|
||||||
proxy_info: any;
|
proxy_info: any;
|
||||||
clearWarningTimer: number | null;
|
clearWarningTimer: number | null;
|
||||||
@ -32,9 +32,7 @@ interface Iweb3Slice {
|
|||||||
const randomBalance = (): string => {
|
const randomBalance = (): string => {
|
||||||
const value = Math.random() * 100;
|
const value = Math.random() * 100;
|
||||||
// 50% 的概率返回整数,50% 的概率返回一位小数
|
// 50% 的概率返回整数,50% 的概率返回一位小数
|
||||||
return Math.random() > 0.5
|
return Math.random() > 0.5 ? Math.floor(value).toString() : value.toFixed(1);
|
||||||
? Math.floor(value).toString()
|
|
||||||
: value.toFixed(1);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 随机生成 1-60 的整数
|
// 随机生成 1-60 的整数
|
||||||
@ -108,8 +106,25 @@ const initialState: Iweb3Slice = {
|
|||||||
},
|
},
|
||||||
clearWarningTimer: null,
|
clearWarningTimer: null,
|
||||||
clearFailTimer: 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: [
|
data: [
|
||||||
{
|
{
|
||||||
country_code: "gl",
|
country_code: "gl",
|
||||||
@ -134,7 +149,7 @@ const initialState: Iweb3Slice = {
|
|||||||
],
|
],
|
||||||
isLine: true,
|
isLine: true,
|
||||||
name: "newHomeProxies",
|
name: "newHomeProxies",
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -174,12 +189,10 @@ export const appSlice = createSlice({
|
|||||||
if (state.proxy_info.proxies.length === 0) return;
|
if (state.proxy_info.proxies.length === 0) return;
|
||||||
|
|
||||||
// 标记所有代理为在线 - 这个操作仍然需要
|
// 标记所有代理为在线 - 这个操作仍然需要
|
||||||
state.proxy_info.proxies = state.proxy_info.proxies.map(
|
state.proxy_info.proxies = state.proxy_info.proxies.map((item: any) => {
|
||||||
(item: any) => {
|
|
||||||
item.isLine = true;
|
item.isLine = true;
|
||||||
return item;
|
return item;
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
// 计算需要添加的钱包数量
|
// 计算需要添加的钱包数量
|
||||||
const proxiesCount = state.proxy_info.proxies.length;
|
const proxiesCount = state.proxy_info.proxies.length;
|
||||||
@ -249,9 +262,7 @@ export const appSlice = createSlice({
|
|||||||
// state.proxy_info.proxies = action.payload;
|
// state.proxy_info.proxies = action.payload;
|
||||||
},
|
},
|
||||||
randomUpdateWeb3List: (state) => {
|
randomUpdateWeb3List: (state) => {
|
||||||
state.web3List = state.web3List.map((wallet) =>
|
state.web3List = state.web3List.map((wallet) => updateWalletData(wallet));
|
||||||
updateWalletData(wallet)
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
randomUpdateWeb3List2: (state) => {
|
randomUpdateWeb3List2: (state) => {
|
||||||
state.web3List2 = state.web3List2.map((wallet) =>
|
state.web3List2 = state.web3List2.map((wallet) =>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user