From 2efe64862d3a3c35b81a4df4eaa034cf837c5b3a Mon Sep 17 00:00:00 2001 From: liyuanhu Date: Wed, 23 Apr 2025 15:02:22 +0800 Subject: [PATCH] fix:bug --- .../svg/layout/anti-dark-analysis-network.svg | 5 + src/layout/index.tsx | 8 +- .../components/world-geo copy 2.tsx | 949 +++++++++++++ .../components/world-geo copy.tsx | 876 ++++++++++++ .../components/world-geo.tsx | 1154 ++++++++++++++++ .../anti-dark-analysis-network/data/index.ts | 1175 +++++++++++++++++ .../data/mockData.ts | 449 +++++++ .../anti-dark-analysis-network/index.scss | 211 +++ .../anti-dark-analysis-network/index.tsx | 309 +++++ src/pages/anti-forensics-forwarding/index.tsx | 4 +- src/routes/index.tsx | 5 + 11 files changed, 5142 insertions(+), 3 deletions(-) create mode 100644 src/assets/svg/layout/anti-dark-analysis-network.svg create mode 100644 src/pages/anti-dark-analysis-network/components/world-geo copy 2.tsx create mode 100644 src/pages/anti-dark-analysis-network/components/world-geo copy.tsx create mode 100644 src/pages/anti-dark-analysis-network/components/world-geo.tsx create mode 100644 src/pages/anti-dark-analysis-network/data/index.ts create mode 100644 src/pages/anti-dark-analysis-network/data/mockData.ts create mode 100644 src/pages/anti-dark-analysis-network/index.scss create mode 100644 src/pages/anti-dark-analysis-network/index.tsx diff --git a/src/assets/svg/layout/anti-dark-analysis-network.svg b/src/assets/svg/layout/anti-dark-analysis-network.svg new file mode 100644 index 0000000..cbad854 --- /dev/null +++ b/src/assets/svg/layout/anti-dark-analysis-network.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/layout/index.tsx b/src/layout/index.tsx index e75bd8e..6318eca 100644 --- a/src/layout/index.tsx +++ b/src/layout/index.tsx @@ -11,6 +11,7 @@ import ChevronDownSvg from "@/assets/svg/layout/chevron-down.svg?react"; import Decentralized from "@/assets/svg/layout/decentralized.svg?react"; import PoolSvg from '@/assets/svg/layout/pool.svg?react' import HomeSvg from '@/assets/svg/layout/home.svg?react' +import AntiDarkAnalysisNetworkSvg from '@/assets/svg/layout/anti-dark-analysis-network.svg?react' import "./index.scss"; import type { RootState } from "@/store"; @@ -37,6 +38,11 @@ export default function Layout() { title: "面向溯源对抗的数据转发", icon: , }, + { + id: "anti-dark-analysis-network", + title: "抗暗特征分析的隐匿网络应用", + icon: , + }, // { // id: 'proxies', // title: '节点池', @@ -74,7 +80,7 @@ export default function Layout() { to={"/" + item.id} className={({ isActive }) => cn( - "px-[11px] py-2 flex items-center gap-2 rounded text-white text-sm", + "pl-[11px] py-2 flex items-center gap-2 rounded text-white text-sm", isActive && "bg-[#213265] " ) } diff --git a/src/pages/anti-dark-analysis-network/components/world-geo copy 2.tsx b/src/pages/anti-dark-analysis-network/components/world-geo copy 2.tsx new file mode 100644 index 0000000..472bb7b --- /dev/null +++ b/src/pages/anti-dark-analysis-network/components/world-geo copy 2.tsx @@ -0,0 +1,949 @@ +import { useEffect, useMemo, useRef, memo } from "react"; +import * as echarts from "echarts"; +// import 'echarts-gl'; +// import { useQueryClient } from "@tanstack/react-query"; +import type { EChartsType } from "echarts"; +import worldGeoJson from "@/assets/echarts-map/json/world.json"; +import { geoCoordMap, countryNameMap, countryCodeMap } from "@/data"; +import { getUrl } from "@/lib/utils"; +import { CONST_TOOLTIP_TYPE } from ".."; +const planePathImg = + "image://data:image/svg+xml;charset=utf-8;base64,PHN2ZyB3aWR0aD0iNjciIGhlaWdodD0iMTAyIiB2aWV3Qm94PSIwIDAgNjcgMTAyIiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8ZyBmaWx0ZXI9InVybCgjZmlsdGVyMF9mXzYxMTdfMjEyNDA3KSI+CjxwYXRoIGQ9Ik0zNC4yMTA5IDkxLjE4ODZMNTMuNjU3OCA0MC45NThDNTQuOTM4IDM3LjY1MTMgNTUuNzk4MyAzNC4xNTkyIDU1LjM1NjMgMzAuNjQxQzU0LjQzNTcgMjMuMzEyOCA1MC40Njg0IDExLjAyMDggMzQuMjExMiAxMS4wMjA4QzE5LjE5MDMgMTEuMDIwOCAxMy45MTEgMjEuNTE0NiAxMi4wNTU0IDI4Ljg5MTJDMTAuOTAxIDMzLjQ4MDYgMTEuOTkyNiAzOC4yMTg2IDEzLjgyMzEgNDIuNTgyN0wzNC4yMTA5IDkxLjE4ODZaIiBmaWxsPSJ1cmwoI3BhaW50MF9saW5lYXJfNjExN18yMTI0MDcpIi8+CjwvZz4KPGRlZnM+CjxmaWx0ZXIgaWQ9ImZpbHRlcjBfZl82MTE3XzIxMjQwNyIgeD0iMC44OTE3NDQiIHk9IjAuMzMxOTkiIHdpZHRoPSI2NS4yNzA3IiBoZWlnaHQ9IjEwMS41NDUiIGZpbHRlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIj4KPGZlRmxvb2QgZmxvb2Qtb3BhY2l0eT0iMCIgcmVzdWx0PSJCYWNrZ3JvdW5kSW1hZ2VGaXgiLz4KPGZlQmxlbmQgbW9kZT0ibm9ybWFsIiBpbj0iU291cmNlR3JhcGhpYyIgaW4yPSJCYWNrZ3JvdW5kSW1hZ2VGaXgiIHJlc3VsdD0ic2hhcGUiLz4KPGZlR2F1c3NpYW5CbHVyIHN0ZERldmlhdGlvbj0iNS4zNDQ0MSIgcmVzdWx0PSJlZmZlY3QxX2ZvcmVncm91bmRCbHVyXzYxMTdfMjEyNDA3Ii8+CjwvZmlsdGVyPgo8bGluZWFyR3JhZGllbnQgaWQ9InBhaW50MF9saW5lYXJfNjExN18yMTI0MDciIHgxPSIzNS4yODI2IiB5MT0iMTAuODU2NCIgeDI9IjM1LjI4MjYiIHkyPSI4Ni44NTY0IiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CjxzdG9wIHN0b3AtY29sb3I9IiMwMEYyRkYiLz4KPHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjMTUwMEZGIi8+CjwvbGluZWFyR3JhZGllbnQ+CjwvZGVmcz4KPC9zdmc+Cg=="; +interface LinesItemType { + name: string; + country_code: string; + value: number[]; +} +type LinesDataType = [LinesItemType, LinesItemType]; +type LinesType = [string, LinesDataType[]]; +// 创建单个国家的涟漪效果 +const createCountryRipple = (countryCode: string) => { + const coords = geoCoordMap[countryCode]; + if (!coords) return null; + return { + name: countryCodeMap[countryCode] ?? "", + value: coords, + country_code: countryCode, + }; +}; +export const WorldGeo = memo( + ({ + screenData, + selectedApp, + tooltipType, + tooltipClosed, + setTooltipClosed, + }: { + screenData: any; + selectedApp: any; + tooltipType: string; + tooltipClosed: boolean; + setTooltipClosed: (value: boolean) => void; + }) => { + // const queryClient = useQueryClient() + const customTooltipRef = useRef(null); + const proxyGeoRef = useRef(null); + const preMainToData = useRef<{ country_code: string }[]>([]); + const lineMidpointsRef = useRef<{id: string, midpoint: number[], fromCountry: string, toCountry: string}[]>([]); + const labelContainerRef = useRef(null); + const labelsRef = useRef([]); + + const mainToData = useMemo(() => { + // 使用新的数据结构 + const proxiesList = + selectedApp && selectedApp?.jumpList + ? [selectedApp.jumpList] + : screenData?.proxy_info?.proxies ?? []; + // 初始化数据数组 - 不再包含 startCountry + const data: any = []; + // 遍历代理列表 + proxiesList.forEach((proxyItem: any) => { + // 检查是否有数据数组 + if (proxyItem.data && Array.isArray(proxyItem.data)) { + // 遍历数据数组中的每个项目 + proxyItem.data.forEach((item: any) => { + // 如果有 ingress_country_code,则添加一对起点和终点 + if (item.ingress_country_code) { + // 添加起点(country_code) + data.push({ + country_code: item.country_code, + type: "start", + isLine: proxyItem.isLine, // 保存连线标志 + }); + // 添加终点(ingress_country_code) + data.push({ + country_code: item.ingress_country_code, + type: "end", + isLine: proxyItem.isLine, // 保存连线标志 + }); + } else { + // 如果没有 ingress_country_code,只添加 country_code + data.push({ + country_code: item.country_code, + isLine: proxyItem.isLine, // 保存连线标志 + }); + } + }); + } + }); + return data; + }, [screenData, selectedApp]); + // 创建自定义提示框DOM元素 + const createCustomTooltip = () => { + // 如果已经存在自定义提示框,则移除它 + if (document.getElementById("custom-fixed-tooltip")) { + document.getElementById("custom-fixed-tooltip")?.remove(); + } + // 创建自定义提示框 + const tooltip = document.createElement("div"); + tooltip.id = "custom-fixed-tooltip"; + tooltip.style.position = "fixed"; + tooltip.style.zIndex = "1000"; + tooltip.style.pointerEvents = "auto"; + tooltip.style.backgroundColor = "transparent"; + // 设置提示框内容 + const currentTooltipType = + CONST_TOOLTIP_TYPE[ + tooltipType as keyof typeof CONST_TOOLTIP_TYPE + ] || CONST_TOOLTIP_TYPE.NESTED_ENCRYPTION; + tooltip.innerHTML = ` +
+ +
+
+
+
${ + currentTooltipType.title + }
+ +
+ + + +
+
+ `; + // 添加到DOM + document.body.appendChild(tooltip); + customTooltipRef.current = tooltip; + // 添加关闭按钮事件 + const closeButton = tooltip.querySelector(".close-icon"); + if (closeButton) { + closeButton.addEventListener("click", () => { + setTooltipClosed(false); + tooltip.remove(); + customTooltipRef.current = null; + }); + } + // 定位提示框 + positionCustomTooltip(); + }; + // 定位自定义提示框 - 优化版本 + const positionCustomTooltip = () => { + if (!customTooltipRef.current || !proxyGeoRef.current) return; + // 找到US点 + const coords = geoCoordMap["CA"]; + if (!coords) return; + try { + // 将地理坐标转换为屏幕坐标 + const screenCoord = proxyGeoRef.current.convertToPixel( + "geo", + coords + ); + if ( + screenCoord && + Array.isArray(screenCoord) && + screenCoord.length === 2 + ) { + // 设置提示框位置 + customTooltipRef.current.style.left = `${ + screenCoord[0] + 232 + 7 + }px`; + customTooltipRef.current.style.top = `${ + screenCoord[1] + 40 - 190 + }px`; + } + } catch (error) { + console.error("Error positioning tooltip:", error); + } + }; + // 主线每个节点tip竖线的经纬度,修改tip 竖线的高度也可以用这个 + const mianLineData = (data: typeof mainToData) => { + return ( + data + .map((item: any) => { + const countryCode = item.country_code.toUpperCase(); + const coords = geoCoordMap[countryCode] as + | [number, number] + | undefined; + if (!coords) return null; + return { + name: countryCodeMap[countryCode], + coords: [coords, [coords[0], coords[1] + 4]], + value: countryCode, + }; + }) + .filter((v: any) => !!v) ?? [] + ); + }; + const getLineItem = ( + preCode: string, + nextCode: string + ): [LinesItemType, LinesItemType] => { + return [ + { + name: countryCodeMap[preCode] ?? "", + value: geoCoordMap[preCode] ?? [], + country_code: preCode, + }, + { + name: countryCodeMap[nextCode] ?? "", + value: geoCoordMap[nextCode] ?? [], + country_code: nextCode, + }, + ]; + }; + const getLine = () => { + // 实现数据处理 + const solidData: LinesType[] = [["main", []]]; // 使用"main"替代startCountry.country_code + // 收集需要显示涟漪效果的所有点(包括连线和不连线的) + const ripplePoints: any[] = []; + // 处理主路径数据 + for (let i = 0; i < mainToData.length; i++) { + // 如果是最后一个元素,则跳过(因为没有下一个元素作为终点) + if (i === mainToData.length - 1) continue; + const currentItem = mainToData[i]; + const nextItem = mainToData[i + 1]; + // 获取当前国家代码 + const countryCode = currentItem.country_code.toUpperCase(); + // 如果当前项是起点,下一项是终点 + if (currentItem.type === "start" && nextItem.type === "end") { + const startCode = countryCode; + const endCode = nextItem.country_code.toUpperCase(); + // 无论是否连线,都添加点的涟漪效果 + const startPoint = createCountryRipple(startCode); + const endPoint = createCountryRipple(endCode); + if (startPoint) ripplePoints.push(startPoint); + if (endPoint) ripplePoints.push(endPoint); + // 检查是否应该绘制连线 + if (currentItem.isLine !== false) { + solidData[0]?.[1].push(getLineItem(startCode, endCode)); + } + // 跳过下一项,因为已经处理了 + i++; + } + // 常规情况:当前项到下一项 + else { + const nextCountryCode = nextItem.country_code.toUpperCase(); + // 无论是否连线,都添加点的涟漪效果 + const currentPoint = createCountryRipple(countryCode); + const nextPoint = createCountryRipple(nextCountryCode); + if (currentPoint) ripplePoints.push(currentPoint); + if (nextPoint) ripplePoints.push(nextPoint); + // 检查是否应该绘制连线 + if (currentItem.isLine !== false) { + solidData[0]?.[1].push( + getLineItem(countryCode, nextCountryCode) + ); + } + } + } + // 虚线数据处理(保持原有逻辑) + const pathList = + screenData?.path_list?.filter( + (v: any) => v.name !== screenData?.proxy_info?.name + ) ?? []; + const otherLineList = pathList.map(() => {}); + return { + solidData, + otherLineList, + ripplePoints, + }; + }; + // 获取连线经纬度数据 + const convertData = (data: LinesDataType[]) => { + const res = []; + const midpoints = []; + + for (let index = 0; index < data.length; index++) { + const dataIndex = data[index]; + const fromCoord = geoCoordMap[dataIndex?.[0]?.country_code ?? ""]; + const toCoord = geoCoordMap[dataIndex?.[1]?.country_code ?? ""]; + const fromCountry = dataIndex?.[0]?.country_code ?? ""; + const toCountry = dataIndex?.[1]?.country_code ?? ""; + + if (fromCoord && toCoord) { + res.push([fromCoord, toCoord]); + + // 计算中点,考虑曲线的弧度 + const curveness = -0.4; // 与飞线弧度相同 + const x1 = fromCoord[0]; + const y1 = fromCoord[1]; + const x2 = toCoord[0]; + const y2 = toCoord[1]; + + // 计算控制点 + const cpx = (x1 + x2) / 2 - (y2 - y1) * curveness; + const cpy = (y1 + y2) / 2 - (x1 - x2) * curveness; + + // 计算曲线上的中点 (t=0.5 时的贝塞尔曲线点) + const midX = x1 * 0.25 + cpx * 0.5 + x2 * 0.25; + const midY = y1 * 0.25 + cpy * 0.5 + y2 * 0.25; + + midpoints.push({ + id: `line-label-${index}`, + midpoint: [midX, midY], + fromCountry, + toCountry + }); + } + } + + // 更新中点引用 + lineMidpointsRef.current = midpoints; + + return res; + }; + // 创建双层点效果 - 大点 + const createDualLayerPoint = ( + lastExit: LinesItemType, + isMainPath: boolean = true + ) => { + // 创建数据数组,用于两个散点图层 + const pointData = lastExit + ? [lastExit].map((v) => { + return { + name: v.name, + value: v.value, + datas: { + country_code: v.country_code, + }, + }; + }) + : []; + // 根据是否是主路径设置不同的大小和颜色 + const outerSize = isMainPath ? 8 : 4; + const innerSize = isMainPath ? 4 : 2; + // Use selectedApp.color if available, otherwise default to blue + const outerColor = selectedApp?.color || "#0ea5e9"; // Use selectedApp.color with fallback + const innerColor = "#FFFFFF"; // 白色内层 + return [ + { + // 外层蓝色点,带涟漪效果 + type: "effectScatter", + coordinateSystem: "geo", + zlevel: 3, + color: outerColor, + symbol: "circle", + symbolSize: outerSize, + rippleEffect: { + period: 8, // 动画时间,值越小速度越快 + brushType: "stroke", // 波纹绘制方式 stroke + scale: 6, // 波纹圆环最大限制,值越大波纹越大 + brushWidth: 2, + }, + label: { + show: false, + }, + tooltip: { + show: false, + trigger: "item", + showContent: true, + alwaysShowContent: true, + formatter: (params: any) => { + // const countryCode = params.data.datas.country_code; + // const countryName = params.data.name; + // 创建自定义HTML提示框 + return ` +
+ +
嵌套加密
+ +
+ `; + }, + backgroundColor: "transparent", + borderWidth: 0, + }, + data: pointData, + } as echarts.SeriesOption, + { + // 内层白色点,不带涟漪效果 + type: "scatter", // 使用普通scatter,不带特效 + coordinateSystem: "geo", + zlevel: 4, // 确保在蓝色点上方 + color: innerColor, + symbol: "circle", + symbolSize: innerSize, + label: { + show: false, + }, + data: pointData, + } as echarts.SeriesOption, + ]; + }; + // 创建路径点的双层效果 + const createPathPoints = ( + dataItems: LinesDataType[], + isMainPath: boolean = true + ) => { + // 创建数据数组 + const pointData = dataItems.map((dataItem: LinesDataType) => { + return { + name: dataItem[0].name, + value: geoCoordMap[dataItem[0].country_code], + datas: { + country_code: dataItem[0].country_code, + }, + }; + }); + // 根据是否是主路径设置不同的大小和颜色 + const outerSize = isMainPath ? 8 : 4; + const innerSize = isMainPath ? 4 : 2; + // Use selectedApp.color if available, otherwise default to blue + const outerColor = selectedApp?.color || "#0ea5e9"; // Use selectedApp.color with fallback + const innerColor = "#FFFFFF"; // 白色内层 + return [ + { + // 外层蓝色点,带涟漪效果 + type: "effectScatter", + coordinateSystem: "geo", + zlevel: 3, + color: outerColor, + symbol: "circle", + symbolSize: outerSize, + rippleEffect: { + period: 8, // 动画时间,值越小速度越快 + brushType: "stroke", // 波纹绘制方式 stroke + scale: 6, // 波纹圆环最大限制,值越大波纹越大 + brushWidth: 2, + }, + label: { + show: false, + }, + tooltip: { + show: false, + trigger: "item", + formatter: (params: any) => { + // const countryCode = params.data.datas.country_code; + // const countryName = params.data.name; + // 创建自定义HTML提示框 + return ` +
+ +
嵌套加密
+ +
+ `; + }, + backgroundColor: "transparent", + borderWidth: 0, + }, + data: pointData, + } as echarts.SeriesOption, + { + // 内层白色点,不带涟漪效果 + type: "scatter", // 使用普通scatter,不带特效 + coordinateSystem: "geo", + zlevel: 4, // 确保在蓝色点上方 + color: innerColor, + symbol: "circle", + symbolSize: innerSize, + label: { + show: false, + }, + data: pointData, + } as echarts.SeriesOption, + ]; + }; + // 创建带自定义提示框的涟漪点 + const createRipplePointsWithTooltip = (ripplePoints: any) => { + // Use selectedApp.color if available, otherwise default to blue + const outerColor = selectedApp?.color || "#0ea5e9"; // Use selectedApp.color with fallback + return { + type: "effectScatter", + coordinateSystem: "geo", + zlevel: 3, + color: outerColor, + symbol: "circle", + symbolSize: 8, + rippleEffect: { + period: 8, + brushType: "stroke", + scale: 6, + brushWidth: 2, + }, + label: { + show: false, + formatter: (params: any) => { + return `{${params.data.datas.country_code}|}`; + }, + }, + // 添加提示框配置 + tooltip: { + show: false, + trigger: "item", + formatter: (params: any) => { + // const countryCode = params.data.datas.country_code; + // const countryName = params.data.name; + // 创建自定义HTML提示框 + return ` +
+ +
嵌套加密
+ +
+ `; + }, + backgroundColor: "transparent", + borderWidth: 0, + }, + data: ripplePoints.map((point: any) => ({ + name: point.name, + value: point.value, + datas: { + country_code: point.country_code, + }, + })), + } as echarts.SeriesOption; + }; + // 连线 series + const getLianData = (series: echarts.SeriesOption[]) => { + const { solidData, otherLineList, ripplePoints } = getLine(); + // 如果有需要显示涟漪效果的点,添加它们 + if (ripplePoints.length > 0) { + // 添加带自定义提示框的外层蓝色点 + series.push(createRipplePointsWithTooltip(ripplePoints)); + // 添加内层白色点,不带涟漪效果 + series.push({ + type: "scatter", // 使用普通scatter,不带特效 + coordinateSystem: "geo", + zlevel: 4, // 确保在蓝色点上方 + color: "#FFFFFF", // 白色内层 + symbol: "circle", + symbolSize: 4, + label: { + show: false, + }, + data: ripplePoints.map((point) => ({ + name: point.name, + value: point.value, + datas: { + country_code: point.country_code, + }, + })), + } as echarts.SeriesOption); + } + solidData.forEach((item) => { + // 如果没有连线数据,则跳过 + if (item[1].length === 0) { + return; + } + const lastExit = item[1]?.[item[1].length - 1]?.[1] ?? null; + // 添加飞行线 + series.push({ + name: item[0], + type: "lines", + zlevel: 1, + label: { + show: false, // 不使用内置标签 + }, + // 飞行线特效 + effect: { + show: true, // 是否显示 + period: 4, // 特效动画时间 + trailLength: 0.7, // 特效尾迹长度。取从 0 到 1 的值,数值越大尾迹越长 + symbol: planePathImg, // 特效图形标记 + symbolSize: [10, 20], + }, + // 线条样式 + lineStyle: { + curveness: -0.4, // 飞线弧度 + type: "solid", // 飞线类型 + color: selectedApp?.color || "#0ea5e9", // Use selectedApp.color with fallback + width: 1.5, // 飞线宽度 + opacity: 0.1, + }, + data: convertData( + item[1] + ) as echarts.LinesSeriesOption["data"], + }); + // 添加路径点的双层效果 + const pathPoints = createPathPoints(item[1], true); + series.push(...pathPoints); + // 添加出口节点的双层效果 + if (lastExit) { + const exitNodes = createDualLayerPoint(lastExit, true); + series.push(...exitNodes); + } + }); + otherLineList.forEach((line: any) => { + line.forEach((item: any) => { + const lastExit = item[1]?.[item[1].length - 1]?.[1] ?? null; + // 添加虚线 + series.push({ + name: item[0], + type: "lines", + zlevel: 1, + label: { + show: false, + }, + // 线条样式 + lineStyle: { + curveness: -0.4, // 飞线弧度 + type: [5, 5], // 飞线类型 + color: "#F0FFA2", // 飞线颜色 + width: 0.5, // 飞线宽度 + opacity: 0.6, + }, + data: convertData( + item[1] + ) as echarts.LinesSeriesOption["data"], + }); + // 添加路径点的双层效果(次要路径) + const pathPoints = createPathPoints(item[1], false); + series.push(...pathPoints); + // 添加出口节点的双层效果(次要路径) + if (lastExit) { + const exitNodes = createDualLayerPoint(lastExit, false); + series.push(...exitNodes); + } + }); + }); + return true; + }; + // 主线tip series + const getMianLineTipData = (series: echarts.SeriesOption[] = []) => { + const rich = Object.keys(countryCodeMap).reduce((object, code) => { + object[code] = { + color: "transparent", + height: 20, + width: 20, + align: "left", + backgroundColor: { + image: getUrl( + `image/res/flag/${code.toUpperCase()}.png` + ), // 动态生成国旗图标 + }, + }; + return object; + }, {} as { [key: string]: { [key: string]: number | string | unknown } }); + series.push( + // 柱状体的主干 + { + name: "solidTip", + type: "lines", + zlevel: 5, + effect: { + show: false, + symbolSize: 5, // 图标大小 + }, + lineStyle: { + width: 0, // 尾迹线条宽度 + color: "#F0FFA2", + opacity: 1, // 尾迹线条透明度 + curveness: 0, // 尾迹线条曲直度 + }, + label: { + show: true, + position: "end", + color: "#fff", + formatter: (parameters) => { + return `{left|} {gap1|}{${parameters.value}|}{gap2|}{name|${parameters.name}}{gap3|}{right|}`; + }, + rich: { + left: { + color: "transparent", + height: 35, + width: 8, + align: "center", + backgroundColor: { + image: `data:image/svg+xml;charset=utf-8;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMSIgaGVpZ2h0PSI1OCIgdmlld0JveD0iMCAwIDExIDU4IiBmaWxsPSJub25lIj4KPHBhdGggZD0iTTExIDU2LjA4ODRIMVY0Ni4wODg0IiBzdHJva2U9IiNGRkZERDMiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIi8+CjxwYXRoIGQ9Ik0xIDExVjFIMTEiIHN0cm9rZT0iI0ZGRkREMyIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiLz4KPHBhdGggZD0iTTguNzI5NDkgMTlWMzkiIHN0cm9rZT0iI0ZGRkREMyIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiLz4KPC9zdmc+`, // 动态生成国旗图标 + }, + }, + gap1: { + height: 35, + width: 8, + }, + ...rich, + gap2: { + height: 35, + width: 6, + }, + name: { + color: "#fff", + align: "center", + lineHeight: 35, + fontSize: 14, + padding: [2, 0, 0, 0], + }, + gap3: { + height: 35, + width: 8, + }, + right: { + color: "transparent", + height: 35, + width: 8, + align: "center", + backgroundColor: { + image: `data:image/svg+xml;charset=utf-8;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNCIgaGVpZ2h0PSI1OCIgdmlld0JveD0iMCAwIDE0IDU4IiBmaWxsPSJub25lIj4KPHBhdGggZD0iTTEyLjczMDIgNDYuMDQzOVY1Ni4wNDM5SDIuNzMwMjIiIHN0cm9rZT0iI0ZGRkREMyIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiLz4KPHBhdGggZD0iTTIuNzMwMjIgMS4wNDM5NUgxMi43MzAyVjExLjA0MzkiIHN0cm9rZT0iI0ZGRkREMyIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiLz4KPHBhdGggZD0iTTEgMTkuMDQzOVYzOS4wNDM5IiBzdHJva2U9IiNGRkZERDMiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIi8+Cjwvc3ZnPg==`, // 动态生成国旗图标 + }, + }, + }, + backgroundColor: "#080A00", + }, + silent: true, + data: mianLineData(mainToData), + } + ); + }; + const isCN = (code: string) => { + return ["HK", "MO", "TW", "CN"].includes(code.toUpperCase()); + }; + + const getOption = () => { + const series: echarts.SeriesOption[] = []; + getLianData(series); + getMianLineTipData(series);// 添加主线tip 暂时隐藏 + + const option = { + backgroundColor: "transparent", + // 全局提示框配置 + tooltip: { + show: true, + trigger: "item", + enterable: true, + confine: true, // 保持提示框在图表范围内 + appendToBody: true, // 将提示框附加到body以获得更好的定位 + // position: function(pos:any, params, dom, rect, size) { + position: function (pos: any) { + // 自定义定位逻辑(如果需要) + return [pos[0] + 10, pos[1] - 50]; // 从光标偏移 + }, + }, + // 底图样式 + geo: { + map: "world", // 地图类型 + roam: true, // 是否开启缩放 + zoom: 1, // 初始缩放大小 + // center: [11.3316626, 19.5845024], // 地图中心点 + layoutCenter: ["50%", "50%"], //地图位置 + scaleLimit: { + // 缩放等级 + min: 1, + max: 3, + }, + label: { + show: false, + }, + nameMap: countryNameMap, // 自定义地区的名称映射 + // 三维地理坐标系样式 + itemStyle: { + areaColor: "#020617", // 修改为要求的填充颜色 + borderColor: "#cbd5e1", // 修改为要求的边框颜色 + borderWidth: 1, // 边框宽度 + borderType: "dashed", // 修改为点线边框 + }, + emphasis: { + itemStyle: { + areaColor: "#172554", // 修改为鼠标悬停时的填充颜色 + borderColor: "#0ea5e9", // 修改为鼠标悬停时的边框颜色 + borderWidth: 1.2, // 修改为鼠标悬停时的边框宽度 + borderType: "solid", // 修改为实线边框 + }, + label: false, + }, + tooltip: { + show: true, + trigger: "item", + triggerOn: "click", // 提示框触发的条件 + enterable: true, // 鼠标是否可进入提示框浮层中,默认为false,如需详情内交互,如添加链接,按钮,可设置为 true + backgroundColor: "rgba(0,0,0,0.8)", + borderColor: "rgba(0,0,0,0.2)", + textStyle: { + color: "#fff", + }, + formatter: (parameters: { + name: string; + data: + | { + name: string; + datas: { tradingCountry: string }; + } + | undefined; + }) => { + if (parameters.data?.name) + return parameters.data.name; + return parameters.name; + }, + }, + }, + series: series, + }; + return option; + }; + + // 创建DOM标签 + const createDOMLabels = () => { + // 清除现有标签 + if (labelContainerRef.current) { + labelContainerRef.current.innerHTML = ''; + labelsRef.current = []; + } else { + // 创建标签容器 + const container = document.createElement('div'); + container.className = 'line-labels-container'; + container.style.position = 'absolute'; + container.style.top = '0'; + container.style.left = '0'; + container.style.pointerEvents = 'none'; + container.style.zIndex = '1000'; + container.style.width = '100%'; + container.style.height = '100%'; + container.style.overflow = 'hidden'; + + // 添加到地图容器 + const chartDom = document.getElementById("screenGeo"); + if (chartDom) { + chartDom.style.position = 'relative'; + chartDom.appendChild(container); + labelContainerRef.current = container; + } + } + + // 创建新标签 + lineMidpointsRef.current.forEach((point, index) => { + const label = document.createElement('div'); + label.id = point.id; + label.className = 'line-label'; + label.style.position = 'absolute'; + label.style.backgroundColor = '#8B3700'; + label.style.color = '#FFB27A'; + label.style.padding = '5px 10px'; + label.style.borderRadius = '4px'; + label.style.fontSize = '18px'; + label.style.fontWeight = 'normal'; + label.style.textAlign = 'center'; + label.style.transform = 'translate(-50%, -50%)'; + label.style.whiteSpace = 'nowrap'; + label.style.pointerEvents = 'none'; + label.style.zIndex = '1001'; + label.textContent = 'SS签名'; + + // 添加到容器 + labelContainerRef.current?.appendChild(label); + labelsRef.current.push(label); + }); + + // 更新标签位置 + updateLabelPositions(); + }; + + // 更新标签位置 + const updateLabelPositions = () => { + if (!proxyGeoRef.current || !labelContainerRef.current) return; + + lineMidpointsRef.current.forEach((point, index) => { + const label = labelsRef.current[index]; + if (!label) return; + + const pixelPoint = proxyGeoRef.current?.convertToPixel('geo', point.midpoint); + if (pixelPoint && Array.isArray(pixelPoint)) { + label.style.left = `${pixelPoint[0]}px`; + label.style.top = `${pixelPoint[1]}px`; + } + }); + }; + + const handleResize = () => { + proxyGeoRef.current?.resize(); + updateLabelPositions(); + }; + + useEffect(() => { + preMainToData.current?.some( + (item, index) => + item.country_code !== mainToData[index]?.country_code + ) && proxyGeoRef.current?.clear(); + preMainToData.current = mainToData; + const option = getOption(); + proxyGeoRef.current?.setOption(option); + + // 创建DOM标签 + setTimeout(createDOMLabels, 100); + }, [screenData, mainToData]); + + useEffect(() => { + const chartDom = document.getElementById("screenGeo"); + proxyGeoRef.current = echarts.init(chartDom); + echarts.registerMap( + "world", + worldGeoJson as unknown as Parameters< + typeof echarts.registerMap + >[1] + ); + const option = getOption(); + option && proxyGeoRef.current?.setOption(option); + + // 添加地图交互事件监听器 + proxyGeoRef.current?.on('georoam', updateLabelPositions); + + // 页面resize时触发 + window.addEventListener("resize", handleResize); + return () => { + window.removeEventListener("resize", handleResize); + proxyGeoRef.current?.off('georoam', updateLabelPositions); + + // 清理DOM标签 + if (labelContainerRef.current) { + labelContainerRef.current.remove(); + labelContainerRef.current = null; + labelsRef.current = []; + } + + proxyGeoRef.current?.dispose(); + proxyGeoRef.current = null; + }; + }, []); + + useEffect(() => { + if (tooltipClosed) { + createCustomTooltip(); + } + }, [tooltipClosed, tooltipType]); + + return ( +
+
+
+ ); + } +); \ No newline at end of file diff --git a/src/pages/anti-dark-analysis-network/components/world-geo copy.tsx b/src/pages/anti-dark-analysis-network/components/world-geo copy.tsx new file mode 100644 index 0000000..33e7b69 --- /dev/null +++ b/src/pages/anti-dark-analysis-network/components/world-geo copy.tsx @@ -0,0 +1,876 @@ +import { useEffect, useMemo, useRef, memo } from "react"; +import * as echarts from "echarts"; +// import 'echarts-gl'; +// import { useQueryClient } from "@tanstack/react-query"; +import type { EChartsType } from "echarts"; +import worldGeoJson from "@/assets/echarts-map/json/world.json"; +import { geoCoordMap, countryNameMap, countryCodeMap } from "@/data"; +import { getUrl } from "@/lib/utils"; +import { CONST_TOOLTIP_TYPE } from ".."; +const planePathImg = + "image://data:image/svg+xml;charset=utf-8;base64,PHN2ZyB3aWR0aD0iNjciIGhlaWdodD0iMTAyIiB2aWV3Qm94PSIwIDAgNjcgMTAyIiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8ZyBmaWx0ZXI9InVybCgjZmlsdGVyMF9mXzYxMTdfMjEyNDA3KSI+CjxwYXRoIGQ9Ik0zNC4yMTA5IDkxLjE4ODZMNTMuNjU3OCA0MC45NThDNTQuOTM4IDM3LjY1MTMgNTUuNzk4MyAzNC4xNTkyIDU1LjM1NjMgMzAuNjQxQzU0LjQzNTcgMjMuMzEyOCA1MC40Njg0IDExLjAyMDggMzQuMjExMiAxMS4wMjA4QzE5LjE5MDMgMTEuMDIwOCAxMy45MTEgMjEuNTE0NiAxMi4wNTU0IDI4Ljg5MTJDMTAuOTAxIDMzLjQ4MDYgMTEuOTkyNiAzOC4yMTg2IDEzLjgyMzEgNDIuNTgyN0wzNC4yMTA5IDkxLjE4ODZaIiBmaWxsPSJ1cmwoI3BhaW50MF9saW5lYXJfNjExN18yMTI0MDcpIi8+CjwvZz4KPGRlZnM+CjxmaWx0ZXIgaWQ9ImZpbHRlcjBfZl82MTE3XzIxMjQwNyIgeD0iMC44OTE3NDQiIHk9IjAuMzMxOTkiIHdpZHRoPSI2NS4yNzA3IiBoZWlnaHQ9IjEwMS41NDUiIGZpbHRlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIj4KPGZlRmxvb2QgZmxvb2Qtb3BhY2l0eT0iMCIgcmVzdWx0PSJCYWNrZ3JvdW5kSW1hZ2VGaXgiLz4KPGZlQmxlbmQgbW9kZT0ibm9ybWFsIiBpbj0iU291cmNlR3JhcGhpYyIgaW4yPSJCYWNrZ3JvdW5kSW1hZ2VGaXgiIHJlc3VsdD0ic2hhcGUiLz4KPGZlR2F1c3NpYW5CbHVyIHN0ZERldmlhdGlvbj0iNS4zNDQ0MSIgcmVzdWx0PSJlZmZlY3QxX2ZvcmVncm91bmRCbHVyXzYxMTdfMjEyNDA3Ii8+CjwvZmlsdGVyPgo8bGluZWFyR3JhZGllbnQgaWQ9InBhaW50MF9saW5lYXJfNjExN18yMTI0MDciIHgxPSIzNS4yODI2IiB5MT0iMTAuODU2NCIgeDI9IjM1LjI4MjYiIHkyPSI4Ni44NTY0IiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CjxzdG9wIHN0b3AtY29sb3I9IiMwMEYyRkYiLz4KPHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjMTUwMEZGIi8+CjwvbGluZWFyR3JhZGllbnQ+CjwvZGVmcz4KPC9zdmc+Cg=="; +interface LinesItemType { + name: string; + country_code: string; + value: number[]; +} +type LinesDataType = [LinesItemType, LinesItemType]; +type LinesType = [string, LinesDataType[]]; +// 创建单个国家的涟漪效果 +const createCountryRipple = (countryCode: string) => { + const coords = geoCoordMap[countryCode]; + if (!coords) return null; + return { + name: countryCodeMap[countryCode] ?? "", + value: coords, + country_code: countryCode, + }; +}; +export const WorldGeo = memo( + ({ + screenData, + selectedApp, + tooltipType, + tooltipClosed, + setTooltipClosed, + }: { + screenData: any; + selectedApp: any; + tooltipType: string; + tooltipClosed: boolean; + setTooltipClosed: (value: boolean) => void; + }) => { + // const queryClient = useQueryClient() + const customTooltipRef = useRef(null); + const proxyGeoRef = useRef(null); + const preMainToData = useRef<{ country_code: string }[]>([]); + const mainToData = useMemo(() => { + // 使用新的数据结构 + const proxiesList = + selectedApp && selectedApp?.jumpList + ? [selectedApp.jumpList] + : screenData?.proxy_info?.proxies ?? []; + // 初始化数据数组 - 不再包含 startCountry + const data: any = []; + // 遍历代理列表 + proxiesList.forEach((proxyItem: any) => { + // 检查是否有数据数组 + if (proxyItem.data && Array.isArray(proxyItem.data)) { + // 遍历数据数组中的每个项目 + proxyItem.data.forEach((item: any) => { + // 如果有 ingress_country_code,则添加一对起点和终点 + if (item.ingress_country_code) { + // 添加起点(country_code) + data.push({ + country_code: item.country_code, + type: "start", + isLine: proxyItem.isLine, // 保存连线标志 + }); + // 添加终点(ingress_country_code) + data.push({ + country_code: item.ingress_country_code, + type: "end", + isLine: proxyItem.isLine, // 保存连线标志 + }); + } else { + // 如果没有 ingress_country_code,只添加 country_code + data.push({ + country_code: item.country_code, + isLine: proxyItem.isLine, // 保存连线标志 + }); + } + }); + } + }); + return data; + }, [screenData, selectedApp]); + // 创建自定义提示框DOM元素 + const createCustomTooltip = () => { + // 如果已经存在自定义提示框,则移除它 + if (document.getElementById("custom-fixed-tooltip")) { + document.getElementById("custom-fixed-tooltip")?.remove(); + } + // 创建自定义提示框 + const tooltip = document.createElement("div"); + tooltip.id = "custom-fixed-tooltip"; + tooltip.style.position = "fixed"; + tooltip.style.zIndex = "1000"; + tooltip.style.pointerEvents = "auto"; + tooltip.style.backgroundColor = "transparent"; + // 设置提示框内容 + const currentTooltipType = + CONST_TOOLTIP_TYPE[ + tooltipType as keyof typeof CONST_TOOLTIP_TYPE + ] || CONST_TOOLTIP_TYPE.NESTED_ENCRYPTION; + tooltip.innerHTML = ` +
+ +
+
+
+
${ + currentTooltipType.title + }
+ +
+ + + +
+
+ `; + // 添加到DOM + document.body.appendChild(tooltip); + customTooltipRef.current = tooltip; + // 添加关闭按钮事件 + const closeButton = tooltip.querySelector(".close-icon"); + if (closeButton) { + closeButton.addEventListener("click", () => { + setTooltipClosed(false); + tooltip.remove(); + customTooltipRef.current = null; + }); + } + // 定位提示框 + positionCustomTooltip(); + }; + // 定位自定义提示框 - 优化版本 + const positionCustomTooltip = () => { + if (!customTooltipRef.current || !proxyGeoRef.current) return; + // 找到US点 + const coords = geoCoordMap["CA"]; + if (!coords) return; + try { + // 将地理坐标转换为屏幕坐标 + const screenCoord = proxyGeoRef.current.convertToPixel( + "geo", + coords + ); + if ( + screenCoord && + Array.isArray(screenCoord) && + screenCoord.length === 2 + ) { + // 设置提示框位置 + // customTooltipRef.current.style.left = `${ + // screenCoord[0] + screenCoord[0] / 2 + 18 + // }px`; + // customTooltipRef.current.style.top = `${ + // screenCoord[1] - screenCoord[1] / 2 + 7 + // }px`; + // 设置提示框位置 + customTooltipRef.current.style.left = `${ + screenCoord[0] + 232 + 7 + }px`; + customTooltipRef.current.style.top = `${ + screenCoord[1] + 40 - 190 + }px`; + } + } catch (error) { + console.error("Error positioning tooltip:", error); + } + }; + // 主线每个节点tip竖线的经纬度 + const mianLineData = (data: typeof mainToData) => { + return ( + data + .map((item: any) => { + const countryCode = item.country_code.toUpperCase(); + const coords = geoCoordMap[countryCode] as + | [number, number] + | undefined; + if (!coords) return null; + return { + name: countryCodeMap[countryCode], + coords: [coords, [coords[0], coords[1] + 4]], + value: countryCode, + }; + }) + .filter((v: any) => !!v) ?? [] + ); + }; + const getLineItem = ( + preCode: string, + nextCode: string + ): [LinesItemType, LinesItemType] => { + return [ + { + name: countryCodeMap[preCode] ?? "", + value: geoCoordMap[preCode] ?? [], + country_code: preCode, + }, + { + name: countryCodeMap[nextCode] ?? "", + value: geoCoordMap[nextCode] ?? [], + country_code: nextCode, + }, + ]; + }; + const getLine = () => { + // 实现数据处理 + const solidData: LinesType[] = [["main", []]]; // 使用"main"替代startCountry.country_code + // 收集需要显示涟漪效果的所有点(包括连线和不连线的) + const ripplePoints: any[] = []; + // 处理主路径数据 + for (let i = 0; i < mainToData.length; i++) { + // 如果是最后一个元素,则跳过(因为没有下一个元素作为终点) + if (i === mainToData.length - 1) continue; + const currentItem = mainToData[i]; + const nextItem = mainToData[i + 1]; + // 获取当前国家代码 + const countryCode = currentItem.country_code.toUpperCase(); + // 如果当前项是起点,下一项是终点 + if (currentItem.type === "start" && nextItem.type === "end") { + const startCode = countryCode; + const endCode = nextItem.country_code.toUpperCase(); + // 无论是否连线,都添加点的涟漪效果 + const startPoint = createCountryRipple(startCode); + const endPoint = createCountryRipple(endCode); + if (startPoint) ripplePoints.push(startPoint); + if (endPoint) ripplePoints.push(endPoint); + // 检查是否应该绘制连线 + if (currentItem.isLine !== false) { + solidData[0]?.[1].push(getLineItem(startCode, endCode)); + } + // 跳过下一项,因为已经处理了 + i++; + } + // 常规情况:当前项到下一项 + else { + const nextCountryCode = nextItem.country_code.toUpperCase(); + // 无论是否连线,都添加点的涟漪效果 + const currentPoint = createCountryRipple(countryCode); + const nextPoint = createCountryRipple(nextCountryCode); + if (currentPoint) ripplePoints.push(currentPoint); + if (nextPoint) ripplePoints.push(nextPoint); + // 检查是否应该绘制连线 + if (currentItem.isLine !== false) { + solidData[0]?.[1].push( + getLineItem(countryCode, nextCountryCode) + ); + } + } + } + // 虚线数据处理(保持原有逻辑) + const pathList = + screenData?.path_list?.filter( + (v: any) => v.name !== screenData?.proxy_info?.name + ) ?? []; + const otherLineList = pathList.map(() => {}); + return { + solidData, + otherLineList, + ripplePoints, + }; + }; + // 获取连线经纬度数据 + const convertData = (data: LinesDataType[]) => { + const res = []; + for (let index = 0; index < data.length; index++) { + const dataIndex = data[index]; + const fromCoord = + geoCoordMap[dataIndex?.[0]?.country_code ?? ""]; + const toCoord = geoCoordMap[dataIndex?.[1]?.country_code ?? ""]; + if (fromCoord && toCoord) { + res.push([fromCoord, toCoord]); + } + } + return res; + }; + // 创建双层点效果 - 大点 + const createDualLayerPoint = ( + lastExit: LinesItemType, + isMainPath: boolean = true + ) => { + // 创建数据数组,用于两个散点图层 + const pointData = lastExit + ? [lastExit].map((v) => { + return { + name: v.name, + value: v.value, + datas: { + country_code: v.country_code, + }, + }; + }) + : []; + // 根据是否是主路径设置不同的大小和颜色 + const outerSize = isMainPath ? 8 : 4; + const innerSize = isMainPath ? 4 : 2; + // Use selectedApp.color if available, otherwise default to blue + const outerColor = selectedApp?.color || "#0ea5e9"; // Use selectedApp.color with fallback + const innerColor = "#FFFFFF"; // 白色内层 + return [ + { + // 外层蓝色点,带涟漪效果 + type: "effectScatter", + coordinateSystem: "geo", + zlevel: 3, + color: outerColor, + symbol: "circle", + symbolSize: outerSize, + rippleEffect: { + period: 8, // 动画时间,值越小速度越快 + brushType: "stroke", // 波纹绘制方式 stroke + scale: 6, // 波纹圆环最大限制,值越大波纹越大 + brushWidth: 2, + }, + label: { + show: false, + }, + tooltip: { + show: false, + trigger: "item", + showContent: true, + alwaysShowContent: true, + formatter: (params: any) => { + // const countryCode = params.data.datas.country_code; + // const countryName = params.data.name; + // 创建自定义HTML提示框 + return ` +
+ +
嵌套加密
+ +
+ `; + }, + backgroundColor: "transparent", + borderWidth: 0, + }, + data: pointData, + } as echarts.SeriesOption, + { + // 内层白色点,不带涟漪效果 + type: "scatter", // 使用普通scatter,不带特效 + coordinateSystem: "geo", + zlevel: 4, // 确保在蓝色点上方 + color: innerColor, + symbol: "circle", + symbolSize: innerSize, + label: { + show: false, + }, + data: pointData, + } as echarts.SeriesOption, + ]; + }; + // 创建路径点的双层效果 + const createPathPoints = ( + dataItems: LinesDataType[], + isMainPath: boolean = true + ) => { + // 创建数据数组 + const pointData = dataItems.map((dataItem: LinesDataType) => { + return { + name: dataItem[0].name, + value: geoCoordMap[dataItem[0].country_code], + datas: { + country_code: dataItem[0].country_code, + }, + }; + }); + // 根据是否是主路径设置不同的大小和颜色 + const outerSize = isMainPath ? 8 : 4; + const innerSize = isMainPath ? 4 : 2; + // Use selectedApp.color if available, otherwise default to blue + const outerColor = selectedApp?.color || "#0ea5e9"; // Use selectedApp.color with fallback + const innerColor = "#FFFFFF"; // 白色内层 + return [ + { + // 外层蓝色点,带涟漪效果 + type: "effectScatter", + coordinateSystem: "geo", + zlevel: 3, + color: outerColor, + symbol: "circle", + symbolSize: outerSize, + rippleEffect: { + period: 8, // 动画时间,值越小速度越快 + brushType: "stroke", // 波纹绘制方式 stroke + scale: 6, // 波纹圆环最大限制,值越大波纹越大 + brushWidth: 2, + }, + label: { + show: false, + }, + tooltip: { + show: false, + trigger: "item", + formatter: (params: any) => { + // const countryCode = params.data.datas.country_code; + // const countryName = params.data.name; + // 创建自定义HTML提示框 + return ` +
+ +
嵌套加密
+ +
+ `; + }, + backgroundColor: "transparent", + borderWidth: 0, + }, + data: pointData, + } as echarts.SeriesOption, + { + // 内层白色点,不带涟漪效果 + type: "scatter", // 使用普通scatter,不带特效 + coordinateSystem: "geo", + zlevel: 4, // 确保在蓝色点上方 + color: innerColor, + symbol: "circle", + symbolSize: innerSize, + label: { + show: false, + }, + data: pointData, + } as echarts.SeriesOption, + ]; + }; + // 创建带自定义提示框的涟漪点 + const createRipplePointsWithTooltip = (ripplePoints: any) => { + // Use selectedApp.color if available, otherwise default to blue + const outerColor = selectedApp?.color || "#0ea5e9"; // Use selectedApp.color with fallback + + return { + type: "effectScatter", + coordinateSystem: "geo", + zlevel: 3, + color: outerColor, + symbol: "circle", + symbolSize: 8, + rippleEffect: { + period: 8, + brushType: "stroke", + scale: 6, + brushWidth: 2, + }, + label: { + show: false, + formatter: (params: any) => { + return `{${params.data.datas.country_code}|}`; + }, + }, + // 添加提示框配置 + tooltip: { + show: false, + trigger: "item", + formatter: (params: any) => { + // const countryCode = params.data.datas.country_code; + // const countryName = params.data.name; + // 创建自定义HTML提示框 + return ` +
+ +
嵌套加密
+ +
+ `; + }, + backgroundColor: "transparent", + borderWidth: 0, + }, + data: ripplePoints.map((point: any) => ({ + name: point.name, + value: point.value, + datas: { + country_code: point.country_code, + }, + })), + } as echarts.SeriesOption; + }; + // 连线 series + const getLianData = (series: echarts.SeriesOption[]) => { + const { solidData, otherLineList, ripplePoints } = getLine(); + console.log(solidData, "solidData"); + console.log(otherLineList, "otherLineList"); + console.log(ripplePoints, "ripplePoints"); + // 如果有需要显示涟漪效果的点,添加它们 + if (ripplePoints.length > 0) { + // 添加带自定义提示框的外层蓝色点 + series.push(createRipplePointsWithTooltip(ripplePoints)); + // 添加内层白色点,不带涟漪效果 + series.push({ + type: "scatter", // 使用普通scatter,不带特效 + coordinateSystem: "geo", + zlevel: 4, // 确保在蓝色点上方 + color: "#FFFFFF", // 白色内层 + symbol: "circle", + symbolSize: 4, + label: { + show: false, + }, + data: ripplePoints.map((point) => ({ + name: point.name, + value: point.value, + datas: { + country_code: point.country_code, + }, + })), + } as echarts.SeriesOption); + } + solidData.forEach((item) => { + // 如果没有连线数据,则跳过 + if (item[1].length === 0) { + return; + } + const lastExit = item[1]?.[item[1].length - 1]?.[1] ?? null; + // 添加飞行线 + series.push({ + name: item[0], // todo ! 需要在飞线中间添加label + type: "lines", + zlevel: 1, + label: { + show: false, + position: "middle", + formatter: (params: any) => { + + // 使用自定义样式的文本 + return ( + "{text|SS签名}" + ); + }, + rich: { + text: { + color: "#FFB27A", // 字体颜色为 #FFB27A + fontSize: 18, // 字体大小为 18px + padding: [10,10,10,10], // padding 上下为 4,左右为 8 + backgroundColor: "#8B3700", // 背景颜色为 #8B3700 + borderRadius: 4, // 可选:添加圆角效果 + }, + }, + // 不需要额外的背景色,因为已经在 rich 中设置了 + backgroundColor: "transparent", + padding: [0, 0, 0, 0], + }, + // 飞行线特效 + effect: { + show: true, // 是否显示 + period: 4, // 特效动画时间 + trailLength: 0.7, // 特效尾迹长度。取从 0 到 1 的值,数值越大尾迹越长 + symbol: planePathImg, // 特效图形标记 + symbolSize: [10, 20], + }, + // 线条样式 + lineStyle: { + curveness: -0.4, // 飞线弧度 + type: "solid", // 飞线类型 + color: selectedApp?.color || "#0ea5e9", // Use selectedApp.color with fallback + width: 1.5, // 飞线宽度 + opacity: 0.1, + }, + data: convertData( + item[1] + ) as echarts.LinesSeriesOption["data"], + }); + // 添加路径点的双层效果 + const pathPoints = createPathPoints(item[1], true); + series.push(...pathPoints); + // 添加出口节点的双层效果 + if (lastExit) { + const exitNodes = createDualLayerPoint(lastExit, true); + series.push(...exitNodes); + } + }); + otherLineList.forEach((line: any) => { + line.forEach((item: any) => { + const lastExit = item[1]?.[item[1].length - 1]?.[1] ?? null; + // 添加虚线 + series.push({ + name: item[0], + type: "lines", + zlevel: 1, + label: { + show: false, + }, + // 线条样式 + lineStyle: { + curveness: -0.4, // 飞线弧度 + type: [5, 5], // 飞线类型 + color: "#F0FFA2", // 飞线颜色 + width: 0.5, // 飞线宽度 + opacity: 0.6, + }, + data: convertData( + item[1] + ) as echarts.LinesSeriesOption["data"], + }); + // 添加路径点的双层效果(次要路径) + const pathPoints = createPathPoints(item[1], false); + series.push(...pathPoints); + // 添加出口节点的双层效果(次要路径) + if (lastExit) { + const exitNodes = createDualLayerPoint(lastExit, false); + series.push(...exitNodes); + } + }); + }); + return true; + }; + // 主线tip series + const getMianLineTipData = (series: echarts.SeriesOption[] = []) => { + const rich = Object.keys(countryCodeMap).reduce((object, code) => { + object[code] = { + color: "transparent", + height: 20, + width: 20, + align: "left", + backgroundColor: { + image: getUrl( + `image/res/flag/${code.toUpperCase()}.png` + ), // 动态生成国旗图标 + }, + }; + return object; + }, {} as { [key: string]: { [key: string]: number | string | unknown } }); + series.push( + // 柱状体的主干 + { + name: "solidTip", + type: "lines", + zlevel: 5, + effect: { + show: false, + symbolSize: 5, // 图标大小 + }, + lineStyle: { + width: 2, // 尾迹线条宽度 + color: "#F0FFA2", + opacity: 1, // 尾迹线条透明度 + curveness: 0, // 尾迹线条曲直度 + }, + label: { + show: true, + position: "end", + color: "#fff", + formatter: (parameters) => { + return `{left|} {gap1|}{${parameters.value}|}{gap2|}{name|${parameters.name}}{gap3|}{right|}`; + }, + rich: { + left: { + color: "transparent", + height: 35, + width: 8, + align: "center", + backgroundColor: { + image: `data:image/svg+xml;charset=utf-8;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMSIgaGVpZ2h0PSI1OCIgdmlld0JveD0iMCAwIDExIDU4IiBmaWxsPSJub25lIj4KPHBhdGggZD0iTTExIDU2LjA4ODRIMVY0Ni4wODg0IiBzdHJva2U9IiNGRkZERDMiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIi8+CjxwYXRoIGQ9Ik0xIDExVjFIMTEiIHN0cm9rZT0iI0ZGRkREMyIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiLz4KPHBhdGggZD0iTTguNzI5NDkgMTlWMzkiIHN0cm9rZT0iI0ZGRkREMyIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiLz4KPC9zdmc+`, // 动态生成国旗图标 + }, + }, + gap1: { + height: 35, + width: 8, + }, + ...rich, + gap2: { + height: 35, + width: 6, + }, + name: { + color: "#fff", + align: "center", + lineHeight: 35, + fontSize: 14, + padding: [2, 0, 0, 0], + }, + gap3: { + height: 35, + width: 8, + }, + right: { + color: "transparent", + height: 35, + width: 8, + align: "center", + backgroundColor: { + image: `data:image/svg+xml;charset=utf-8;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNCIgaGVpZ2h0PSI1OCIgdmlld0JveD0iMCAwIDE0IDU4IiBmaWxsPSJub25lIj4KPHBhdGggZD0iTTEyLjczMDIgNDYuMDQzOVY1Ni4wNDM5SDIuNzMwMjIiIHN0cm9rZT0iI0ZGRkREMyIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiLz4KPHBhdGggZD0iTTIuNzMwMjIgMS4wNDM5NUgxMi43MzAyVjExLjA0MzkiIHN0cm9rZT0iI0ZGRkREMyIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiLz4KPHBhdGggZD0iTTEgMTkuMDQzOVYzOS4wNDM5IiBzdHJva2U9IiNGRkZERDMiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIi8+Cjwvc3ZnPg==`, // 动态生成国旗图标 + }, + }, + }, + backgroundColor: "#080A00", + }, + silent: true, + data: mianLineData(mainToData), + } + ); + }; + const isCN = (code: string) => { + return ["HK", "MO", "TW", "CN"].includes(code.toUpperCase()); + }; + const getRegions = () => { + const codeList: string[] = []; + const regionsData = mainToData; + regionsData.forEach((item: any) => + codeList.push( + isCN(item.country_code) + ? "CN" + : item.country_code.toUpperCase() + ) + ); + const regions = codeList.map((item) => { + return { + name: countryCodeMap[item], // 中国 + itemStyle: { + color: "#172554", + areaColor: "#172554", + borderColor: "#0ea5e9", // 边框颜色 + borderWidth: 1.2, // 边框宽度 + borderType: "solid", // 修改为实线边框 + }, + }; + }); + return regions; + }; + const getOption = () => { + const series: echarts.SeriesOption[] = []; + getLianData(series); + // getMianLineTipData(series);// 添加主线tip 暂时隐藏 + const regions = getRegions(); + const option = { + backgroundColor: "transparent", + // 全局提示框配置 + tooltip: { + show: true, + trigger: "item", + enterable: true, + confine: true, // 保持提示框在图表范围内 + appendToBody: true, // 将提示框附加到body以获得更好的定位 + // position: function(pos:any, params, dom, rect, size) { + position: function (pos: any) { + // 自定义定位逻辑(如果需要) + return [pos[0] + 10, pos[1] - 50]; // 从光标偏移 + }, + }, + // 底图样式 + geo: { + map: "world", // 地图类型 + roam: true, // 是否开启缩放 + zoom: 1, // 初始缩放大小 + // center: [11.3316626, 19.5845024], // 地图中心点 + layoutCenter: ["50%", "50%"], //地图位置 + scaleLimit: { + // 缩放等级 + min: 1, + max: 3, + }, + label: { + show: false, + }, + nameMap: countryNameMap, // 自定义地区的名称映射 + // 三维地理坐标系样式 + itemStyle: { + areaColor: "#020617", // 修改为要求的填充颜色 + borderColor: "#cbd5e1", // 修改为要求的边框颜色 + borderWidth: 1, // 边框宽度 + borderType: "dashed", // 修改为点线边框 + }, + emphasis: { + itemStyle: { + areaColor: "#172554", // 修改为鼠标悬停时的填充颜色 + borderColor: "#0ea5e9", // 修改为鼠标悬停时的边框颜色 + borderWidth: 1.2, // 修改为鼠标悬停时的边框宽度 + borderType: "solid", // 修改为实线边框 + }, + label: false, + }, + tooltip: { + show: true, + trigger: "item", + triggerOn: "click", // 提示框触发的条件 + enterable: true, // 鼠标是否可进入提示框浮层中,默认为false,如需详情内交互,如添加链接,按钮,可设置为 true + backgroundColor: "rgba(0,0,0,0.8)", + borderColor: "rgba(0,0,0,0.2)", + textStyle: { + color: "#fff", + }, + formatter: (parameters: { + name: string; + data: + | { + name: string; + datas: { tradingCountry: string }; + } + | undefined; + }) => { + if (parameters.data?.name) + return parameters.data.name; + return parameters.name; + }, + }, + regions, + }, + series: series, + }; + return option; + }; + const handleResize = () => { + proxyGeoRef.current?.resize(); + }; + useEffect(() => { + preMainToData.current?.some( + (item, index) => + item.country_code !== mainToData[index]?.country_code + ) && proxyGeoRef.current?.clear(); + preMainToData.current = mainToData; + const option = getOption(); + proxyGeoRef.current?.setOption(option); + }, [screenData, mainToData]); + useEffect(() => { + const chartDom = document.getElementById("screenGeo"); + proxyGeoRef.current = echarts.init(chartDom); + echarts.registerMap( + "world", + worldGeoJson as unknown as Parameters< + typeof echarts.registerMap + >[1] + ); + const option = getOption(); + option && proxyGeoRef.current?.setOption(option); + // 页面resize时触发 + window.addEventListener("resize", handleResize); + return () => { + window.removeEventListener("resize", handleResize); + proxyGeoRef.current?.dispose(); + proxyGeoRef.current = null; + }; + }, []); + useEffect(() => { + if (tooltipClosed) { + createCustomTooltip(); + } + }, [tooltipClosed, tooltipType]); + return ( +
+
+
+ ); + } +); diff --git a/src/pages/anti-dark-analysis-network/components/world-geo.tsx b/src/pages/anti-dark-analysis-network/components/world-geo.tsx new file mode 100644 index 0000000..3eb0745 --- /dev/null +++ b/src/pages/anti-dark-analysis-network/components/world-geo.tsx @@ -0,0 +1,1154 @@ +import { useEffect, useMemo, useRef, memo } from "react"; +import * as echarts from "echarts"; +// import 'echarts-gl'; +// import { useQueryClient } from "@tanstack/react-query"; +import type { EChartsType } from "echarts"; +import worldGeoJson from "@/assets/echarts-map/json/world.json"; +import { geoCoordMap, countryNameMap, countryCodeMap } from "@/data"; +import { getUrl } from "@/lib/utils"; +import { CONST_TOOLTIP_TYPE } from "@/pages/anti-forensics-forwarding"; +const planePathImg = + "image://data:image/svg+xml;charset=utf-8;base64,PHN2ZyB3aWR0aD0iNjciIGhlaWdodD0iMTAyIiB2aWV3Qm94PSIwIDAgNjcgMTAyIiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8ZyBmaWx0ZXI9InVybCgjZmlsdGVyMF9mXzYxMTdfMjEyNDA3KSI+CjxwYXRoIGQ9Ik0zNC4yMTA5IDkxLjE4ODZMNTMuNjU3OCA0MC45NThDNTQuOTM4IDM3LjY1MTMgNTUuNzk4MyAzNC4xNTkyIDU1LjM1NjMgMzAuNjQxQzU0LjQzNTcgMjMuMzEyOCA1MC40Njg0IDExLjAyMDggMzQuMjExMiAxMS4wMjA4QzE5LjE5MDMgMTEuMDIwOCAxMy45MTEgMjEuNTE0NiAxMi4wNTU0IDI4Ljg5MTJDMTAuOTAxIDMzLjQ4MDYgMTEuOTkyNiAzOC4yMTg2IDEzLjgyMzEgNDIuNTgyN0wzNC4yMTA5IDkxLjE4ODZaIiBmaWxsPSJ1cmwoI3BhaW50MF9saW5lYXJfNjExN18yMTI0MDcpIi8+CjwvZz4KPGRlZnM+CjxmaWx0ZXIgaWQ9ImZpbHRlcjBfZl82MTE3XzIxMjQwNyIgeD0iMC44OTE3NDQiIHk9IjAuMzMxOTkiIHdpZHRoPSI2NS4yNzA3IiBoZWlnaHQ9IjEwMS41NDUiIGZpbHRlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIj4KPGZlRmxvb2QgZmxvb2Qtb3BhY2l0eT0iMCIgcmVzdWx0PSJCYWNrZ3JvdW5kSW1hZ2VGaXgiLz4KPGZlQmxlbmQgbW9kZT0ibm9ybWFsIiBpbj0iU291cmNlR3JhcGhpYyIgaW4yPSJCYWNrZ3JvdW5kSW1hZ2VGaXgiIHJlc3VsdD0ic2hhcGUiLz4KPGZlR2F1c3NpYW5CbHVyIHN0ZERldmlhdGlvbj0iNS4zNDQ0MSIgcmVzdWx0PSJlZmZlY3QxX2ZvcmVncm91bmRCbHVyXzYxMTdfMjEyNDA3Ii8+CjwvZmlsdGVyPgo8bGluZWFyR3JhZGllbnQgaWQ9InBhaW50MF9saW5lYXJfNjExN18yMTI0MDciIHgxPSIzNS4yODI2IiB5MT0iMTAuODU2NCIgeDI9IjM1LjI4MjYiIHkyPSI4Ni44NTY0IiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CjxzdG9wIHN0b3AtY29sb3I9IiMwMEYyRkYiLz4KPHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjMTUwMEZGIi8+CjwvbGluZWFyR3JhZGllbnQ+CjwvZGVmcz4KPC9zdmc+Cg=="; +interface LinesItemType { + name: string; + country_code: string; + value: number[]; + color?: string; // 添加颜色属性 +} +type LinesDataType = [LinesItemType, LinesItemType]; +type LinesType = [string, LinesDataType[]]; +// 创建单个国家的涟漪效果 +const createCountryRipple = (countryCode: string, color?: string) => { + const coords = geoCoordMap[countryCode]; + if (!coords) return null; + return { + name: countryCodeMap[countryCode] ?? "", + value: coords, + country_code: countryCode, + color: color || "#0ea5e9", // 添加颜色属性,如果没有则使用默认颜色 + }; +}; +export const WorldGeo = memo( + ({ + currentValue, + newHomeProxies, + tooltipType, + tooltipClosed, + setTooltipClosed, + }: { + currentValue: any; + newHomeProxies: any; + tooltipType: string; + tooltipClosed: boolean; + setTooltipClosed: (value: boolean) => void; + }) => { + // const queryClient = useQueryClient() + // 嵌套加密ref + const customTooltipRef = useRef(null); + // 流量混淆ref + const customTooltip2Ref = useRef(null); + const proxyGeoRef = useRef(null); + const preMainToData = useRef<{ country_code: string }[]>([]); + const lineMidpointsRef = useRef< + { + id: string; + midpoint: number[]; + fromCountry: string; + toCountry: string; + }[] + >([]); + const labelContainerRef = useRef(null); + const labelsRef = useRef([]); + const mainToData = useMemo(() => { + // 使用新的数据结构 + const proxiesList = currentValue ?? []; + // 初始化数据数组 - 不再包含 startCountry + const data: any = []; + // 遍历代理列表 + proxiesList.forEach((proxyItem: any) => { + // 检查是否有数据数组 + if (proxyItem.data && Array.isArray(proxyItem.data)) { + // 遍历数据数组中的每个项目 + proxyItem.data.forEach((item: any) => { + // 如果有 ingress_country_code,则添加一对起点和终点 + if (item.ingress_country_code) { + // 添加起点(country_code) + data.push({ + country_code: item.country_code, + type: "start", + isLine: proxyItem.isLine, // 保存连线标志 + color: proxyItem.color || item.color, // 添加颜色属性 + }); + // 添加终点(ingress_country_code) + data.push({ + country_code: item.ingress_country_code, + type: "end", + isLine: proxyItem.isLine, // 保存连线标志 + color: proxyItem.color || item.color, // 添加颜色属性 + }); + } else { + // 如果没有 ingress_country_code,只添加 country_code + data.push({ + country_code: item.country_code, + isLine: proxyItem.isLine, // 保存连线标志 + color: proxyItem.color || item.color, // 添加颜色属性 + }); + } + }); + } + }); + return data; + }, [currentValue]); + // 创建自定义提示框DOM元素 + const createCustomTooltip = () => { + // 如果已经存在自定义提示框,则移除它 + if (document.getElementById("custom-fixed-tooltip")) { + document.getElementById("custom-fixed-tooltip")?.remove(); + } + // 创建自定义提示框 + const tooltip = document.createElement("div"); + tooltip.id = "custom-fixed-tooltip"; + tooltip.style.position = "fixed"; + tooltip.style.zIndex = "1000"; + tooltip.style.pointerEvents = "auto"; + tooltip.style.backgroundColor = "transparent"; + // 设置提示框内容 + const currentTooltipType = + CONST_TOOLTIP_TYPE[tooltipType as keyof typeof CONST_TOOLTIP_TYPE] || + CONST_TOOLTIP_TYPE.NESTED_ENCRYPTION; + tooltip.innerHTML = ` +
+ +
+
+
+
${ + currentTooltipType.title + }
+ +
+ + + +
+
+ `; + // 添加到DOM + document.body.appendChild(tooltip); + customTooltipRef.current = tooltip; + // 添加关闭按钮事件 + const closeButton = tooltip.querySelector(".close-icon"); + if (closeButton) { + closeButton.addEventListener("click", () => { + setTooltipClosed(false); + tooltip.remove(); + customTooltipRef.current = null; + }); + } + // 定位提示框 + positionCustomTooltip(); + }; + // 定位自定义提示框 - 优化版本 + const positionCustomTooltip = () => { + if (!customTooltipRef.current || !proxyGeoRef.current) return; + // 找到US点 + const coords = geoCoordMap[currentValue?.[0]?.code ?? "GL"]; + if (!coords) return; + try { + // 将地理坐标转换为屏幕坐标 + const screenCoord = proxyGeoRef.current.convertToPixel("geo", coords); + if ( + screenCoord && + Array.isArray(screenCoord) && + screenCoord.length === 2 + ) { + // 设置提示框位置 + customTooltipRef.current.style.left = `${screenCoord[0] + 232 + 7}px`; + customTooltipRef.current.style.top = `${screenCoord[1] + 40 - 190}px`; + } + } catch (error) { + console.error("Error positioning tooltip:", error); + } + }; + // 创建自定义提示框DOM元素 + const createCustomTooltip2 = () => { + // 如果已经存在自定义提示框,则移除它 + if (document.getElementById("custom-fixed-tooltip2")) { + document.getElementById("custom-fixed-tooltip2")?.remove(); + } + // 创建自定义提示框 + const tooltip = document.createElement("div"); + tooltip.id = "custom-fixed-tooltip2"; + tooltip.style.position = "fixed"; + tooltip.style.zIndex = "1000"; + tooltip.style.pointerEvents = "auto"; + tooltip.style.backgroundColor = "transparent"; + tooltip.innerHTML = ` +
+
+
+
+
流量混淆
+ +
+ + + +
+ +
+ `; + // 添加到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[currentValue?.[0]?.code ?? "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`; + customTooltip2Ref.current.style.top = `${ + screenCoord[1] + 40 - 218 + }px`; + } + } catch (error) { + console.error("Error positioning tooltip:", error); + } + }; + const getLineItem = ( + preCode: string, + nextCode: string + ): [LinesItemType, LinesItemType] => { + return [ + { + name: countryCodeMap[preCode] ?? "", + value: geoCoordMap[preCode] ?? [], + country_code: preCode, + }, + { + name: countryCodeMap[nextCode] ?? "", + value: geoCoordMap[nextCode] ?? [], + country_code: nextCode, + }, + ]; + }; + const getLine = () => { + // 实现数据处理 + const solidData: LinesType[] = [["main", []]]; // 使用"main"替代startCountry.country_code + // 收集需要显示涟漪效果的所有点(包括连线和不连线的) + const ripplePoints: any[] = []; + + // 处理主路径数据 + for (let i = 0; i < mainToData.length; i++) { + // 如果是最后一个元素,则跳过(因为没有下一个元素作为终点) + if (i === mainToData.length - 1) continue; + const currentItem = mainToData[i]; + const nextItem = mainToData[i + 1]; + + // 获取当前国家代码和颜色 + const countryCode = currentItem.country_code.toUpperCase(); + const color = currentItem.color || "#0ea5e9"; // 获取颜色,如果没有则使用默认颜色 + + // 如果当前项是起点,下一项是终点 + if (currentItem.type === "start" && nextItem.type === "end") { + const startCode = countryCode; + const endCode = nextItem.country_code.toUpperCase(); + + // 无论是否连线,都添加点的涟漪效果 + const startPoint = createCountryRipple(startCode, color); + const endPoint = createCountryRipple( + endCode, + nextItem.color || color + ); + if (startPoint) ripplePoints.push(startPoint); + if (endPoint) ripplePoints.push(endPoint); + + // 检查是否应该绘制连线 + if (currentItem.isLine !== false) { + const lineItem = getLineItem(startCode, endCode); + // 添加颜色属性 + lineItem[0].color = color; + lineItem[1].color = nextItem.color || color; + solidData[0]?.[1].push(lineItem); + } + + // 跳过下一项,因为已经处理了 + i++; + } + // 常规情况:当前项到下一项 + else { + const nextCountryCode = nextItem.country_code.toUpperCase(); + + // 无论是否连线,都添加点的涟漪效果 + const currentPoint = createCountryRipple(countryCode, color); + const nextPoint = createCountryRipple( + nextCountryCode, + nextItem.color || color + ); + if (currentPoint) ripplePoints.push(currentPoint); + if (nextPoint) ripplePoints.push(nextPoint); + + // 检查是否应该绘制连线 + if (currentItem.isLine !== false) { + const lineItem = getLineItem(countryCode, nextCountryCode); + // 添加颜色属性 + lineItem[0].color = color; + lineItem[1].color = nextItem.color || color; + solidData[0]?.[1].push(lineItem); + } + } + } + + // 虚线数据处理(保持原有逻辑) + const otherLineList: any = []; + return { + solidData, + otherLineList, + ripplePoints, + }; + }; + // 获取连线经纬度数据 + const convertData = (data: LinesDataType[]) => { + const res = []; + const midpoints = []; + for (let index = 0; index < data.length; index++) { + const dataIndex = data[index]; + const fromCoord = geoCoordMap[dataIndex?.[0]?.country_code ?? ""]; + const toCoord = geoCoordMap[dataIndex?.[1]?.country_code ?? ""]; + const fromCountry = dataIndex?.[0]?.country_code ?? ""; + const toCountry = dataIndex?.[1]?.country_code ?? ""; + if (fromCoord && toCoord) { + res.push({ + coords: [fromCoord, toCoord], + // 添加颜色属性 + lineStyle: { + color: dataIndex?.[0]?.color || "#0ea5e9", + }, + }); + // 计算中点,考虑曲线的弧度 + const curveness = -0.4; // 与飞线弧度相同 + const x1 = fromCoord[0]; + const y1 = fromCoord[1]; + const x2 = toCoord[0]; + const y2 = toCoord[1]; + // 计算控制点 + const cpx = (x1 + x2) / 2 - (y2 - y1) * curveness; + const cpy = (y1 + y2) / 2 - (x1 - x2) * curveness; + // 计算曲线上的中点 (t=0.5 时的贝塞尔曲线点) + const midX = x1 * 0.25 + cpx * 0.5 + x2 * 0.25; + const midY = y1 * 0.25 + cpy * 0.5 + y2 * 0.25; + midpoints.push({ + id: `line-label-${index}`, + midpoint: [midX, midY], + fromCountry, + toCountry, + }); + } + } + // 更新中点引用 + // lineMidpointsRef.current = midpoints; + return res; + }; + // 创建双层点效果 - 大点 + const createDualLayerPoint = ( + lastExit: LinesItemType, + isMainPath: boolean = true, + color?: string + ) => { + // 创建数据数组,用于两个散点图层 + const pointData = lastExit + ? [lastExit].map((v) => { + return { + name: v.name, + value: v.value, + datas: { + country_code: v.country_code, + color: v.color, // 添加颜色属性 + }, + }; + }) + : []; + // 根据是否是主路径设置不同的大小和颜色 + const outerSize = isMainPath ? 8 : 4; + const innerSize = isMainPath ? 4 : 2; + // 使用传入的颜色或从数据中获取颜色,如果都没有则使用默认颜色 + const outerColor = color || lastExit?.color || "#0ea5e9"; + const innerColor = "#FFFFFF"; // 白色内层 + return [ + { + // 外层蓝色点,带涟漪效果 + type: "effectScatter", + coordinateSystem: "geo", + zlevel: 3, + itemStyle: { + color: outerColor, + }, + symbol: "circle", + symbolSize: outerSize, + rippleEffect: { + period: 8, // 动画时间,值越小速度越快 + brushType: "stroke", // 波纹绘制方式 stroke + scale: 6, // 波纹圆环最大限制,值越大波纹越大 + brushWidth: 2, + }, + label: { + show: false, + }, + tooltip: { + show: false, + trigger: "item", + showContent: true, + alwaysShowContent: true, + formatter: (params: any) => { + // const countryCode = params.data.datas.country_code; + // const countryName = params.data.name; + // 创建自定义HTML提示框 + return ` +
+ +
嵌套加密
+ +
+ `; + }, + backgroundColor: "transparent", + borderWidth: 0, + }, + data: pointData, + } as echarts.SeriesOption, + { + // 内层白色点,不带涟漪效果 + type: "scatter", // 使用普通scatter,不带特效 + coordinateSystem: "geo", + zlevel: 4, // 确保在蓝色点上方 + color: innerColor, + symbol: "circle", + symbolSize: innerSize, + label: { + show: false, + }, + data: pointData, + } as echarts.SeriesOption, + ]; + }; + // 添加新方法:根据经纬度数组创建蓝色涟漪小点(不包含白色内层点) + const createRipplePointsFromCoordinates = ( + coordinates: [number, number][], + series: echarts.SeriesOption[] + ) => { + if (!coordinates || coordinates.length === 0) return; + // 使用selectedApp.color或默认蓝色 + const outerColor = "#01FF5E"; + // 只创建外层带涟漪效果的点 + series.push({ + type: "effectScatter", + coordinateSystem: "geo", + zlevel: 3, + color: outerColor, + symbol: "circle", + symbolSize: 6, + rippleEffect: { + period: 8, // 动画时间 + brushType: "stroke", // 波纹绘制方式 + scale: 6, // 波纹圆环最大限制 + brushWidth: 2, + }, + label: { + show: false, + }, + data: coordinates.map((coord) => ({ + name: "", // 可以根据需要添加名称 + value: coord, + })), + } as echarts.SeriesOption); + }; + // 创建路径点的双层效果 + const createPathPoints = ( + dataItems: LinesDataType[], + isMainPath: boolean = true, + color?: string + ) => { + // 创建数据数组 + const pointData = dataItems.map((dataItem: LinesDataType) => { + return { + name: dataItem[0].name, + value: geoCoordMap[dataItem[0].country_code], + datas: { + country_code: dataItem[0].country_code, + color: dataItem[0].color, // 添加颜色属性 + }, + }; + }); + // 根据是否是主路径设置不同的大小和颜色 + const outerSize = isMainPath ? 8 : 4; + const innerSize = isMainPath ? 4 : 2; + // 使用传入的颜色或从数据中获取颜色,如果都没有则使用默认颜色 + const outerColor = color || dataItems[0]?.[0]?.color || "#0ea5e9"; + const innerColor = "#FFFFFF"; // 白色内层 + return [ + { + // 外层蓝色点,带涟漪效果 + type: "effectScatter", + coordinateSystem: "geo", + zlevel: 3, + itemStyle: { + color: outerColor, + }, + symbol: "circle", + symbolSize: outerSize, + rippleEffect: { + period: 8, // 动画时间,值越小速度越快 + brushType: "stroke", // 波纹绘制方式 stroke + scale: 6, // 波纹圆环最大限制,值越大波纹越大 + brushWidth: 2, + }, + label: { + show: false, + }, + tooltip: { + show: false, + trigger: "item", + formatter: (params: any) => { + // const countryCode = params.data.datas.country_code; + // const countryName = params.data.name; + // 创建自定义HTML提示框 + return ` +
+ +
嵌套加密
+ +
+ `; + }, + backgroundColor: "transparent", + borderWidth: 0, + }, + data: pointData, + } as echarts.SeriesOption, + { + // 内层白色点,不带涟漪效果 + type: "scatter", // 使用普通scatter,不带特效 + coordinateSystem: "geo", + zlevel: 4, // 确保在蓝色点上方 + color: innerColor, + symbol: "circle", + symbolSize: innerSize, + label: { + show: false, + }, + data: pointData, + } as echarts.SeriesOption, + ]; + }; + // 创建带自定义提示框的涟漪点 + const createRipplePointsWithTooltip = (ripplePoints: any) => { + return { + type: "effectScatter", + coordinateSystem: "geo", + zlevel: 3, + // 使用函数动态设置每个点的颜色 + itemStyle: { + color: (params: any) => { + return params.data.datas?.color || "#0ea5e9"; // 使用点的颜色或默认颜色 + }, + }, + symbol: "circle", + symbolSize: 8, + rippleEffect: { + period: 8, + brushType: "stroke", + scale: 6, + brushWidth: 2, + }, + label: { + show: false, + formatter: (params: any) => { + return `{${params.data.datas.country_code}|}`; + }, + }, + // 添加提示框配置 + tooltip: { + show: false, + trigger: "item", + formatter: (params: any) => { + // const countryCode = params.data.datas.country_code; + // const countryName = params.data.name; + // 创建自定义HTML提示框 + return ` +
+ +
嵌套加密
+ +
+ `; + }, + backgroundColor: "transparent", + borderWidth: 0, + }, + data: ripplePoints.map((point: any) => ({ + name: point.name, + value: point.value, + datas: { + country_code: point.country_code, + color: point.color, // 添加颜色属性 + }, + })), + } as echarts.SeriesOption; + }; + // 连线 series + const getLianData = (series: echarts.SeriesOption[]) => { + const { solidData, otherLineList, ripplePoints } = getLine(); + // 如果有需要显示涟漪效果的点,添加它们 + if (ripplePoints.length > 0) { + // 添加带自定义提示框的外层蓝色点 + series.push(createRipplePointsWithTooltip(ripplePoints)); + // 添加内层白色点,不带涟漪效果 + series.push({ + type: "scatter", // 使用普通scatter,不带特效 + coordinateSystem: "geo", + zlevel: 4, // 确保在蓝色点上方 + color: "#FFFFFF", // 白色内层 + symbol: "circle", + symbolSize: 4, + label: { + show: false, + }, + data: ripplePoints.map((point) => ({ + name: point.name, + value: point.value, + datas: { + country_code: point.country_code, + color: point.color, // 添加颜色属性 + }, + })), + } as echarts.SeriesOption); + } + solidData.forEach((item) => { + // 如果没有连线数据,则跳过 + if (item[1].length === 0) { + return; + } + const lastExit = item[1]?.[item[1].length - 1]?.[1] ?? null; + // 获取当前路径的颜色 + const pathColor = item[1]?.[0]?.[0]?.color || "#0ea5e9"; // 从第一个点获取颜色,如果没有则使用默认颜色 + + // 添加飞行线 + series.push({ + name: item[0], + type: "lines", + zlevel: 1, + label: { + show: false, // 不使用内置标签 + }, + // 飞行线特效 + effect: { + show: true, // 是否显示 + period: 4, // 特效动画时间 + trailLength: 0.7, // 特效尾迹长度。取从 0 到 1 的值,数值越大尾迹越长 + color: pathColor, // 特效颜色 + // symbol: planePathImg, // 特效图形标记 + symbolSize: [10, 20], + }, + // 线条样式 + lineStyle: { + curveness: -0.4, // 飞线弧度 + type: "solid", // 飞线类型 + color: pathColor, // 使用从数据中获取的颜色 + width: 1.5, // 飞线宽度 + opacity: 0.1, + }, + data: convertData(item[1]) as echarts.LinesSeriesOption["data"], + }); + + // 添加路径点的双层效果 + const pathPoints = createPathPoints(item[1], true, pathColor); + series.push(...pathPoints); + + // 添加出口节点的双层效果 + if (lastExit) { + const exitNodes = createDualLayerPoint(lastExit, true, pathColor); + series.push(...exitNodes); + } + }); + otherLineList.forEach((line: any) => { + line.forEach((item: any) => { + const lastExit = item[1]?.[item[1].length - 1]?.[1] ?? null; + // 获取当前路径的颜色 + const pathColor = item[1]?.[0]?.[0]?.color || "#F0FFA2"; // 从第一个点获取颜色,如果没有则使用默认颜色 + + // 添加虚线 + series.push({ + name: item[0], + type: "lines", + zlevel: 1, + label: { + show: false, + }, + // 线条样式 + lineStyle: { + curveness: -0.4, // 飞线弧度 + type: [5, 5], // 飞线类型 + color: pathColor, // 使用从数据中获取的颜色 + width: 0.5, // 飞线宽度 + opacity: 0.6, + }, + data: convertData(item[1]) as echarts.LinesSeriesOption["data"], + }); + // 添加路径点的双层效果(次要路径) + const pathPoints = createPathPoints(item[1], false, pathColor); + series.push(...pathPoints); + // 添加出口节点的双层效果(次要路径) + if (lastExit) { + const exitNodes = createDualLayerPoint(lastExit, false, pathColor); + series.push(...exitNodes); + } + }); + }); + return true; + }; + + // 创建A点和B点,并添加飞线和标签 + const createSpecialPoints = (series: echarts.SeriesOption[]) => { + // 定义点A和点B的坐标 + const pointA = geoCoordMap[currentValue[0]?.startPoint ?? "GL"]; + const pointB = geoCoordMap[currentValue[0]?.endPoint ?? "CA"]; + const newPointB = [pointB[0] + 14, pointB[1] + 10]; + // 添加A点 - 带涟漪效果的双层点 + series.push( + // 外层带涟漪效果的点 + { + type: "effectScatter", + coordinateSystem: "geo", + zlevel: 3, + color: "#FF6B01", // 橙色外层 + symbol: "circle", + symbolSize: 8, + rippleEffect: { + period: 8, // 动画时间 + brushType: "stroke", // 波纹绘制方式 + scale: 6, // 波纹圆环最大限制 + brushWidth: 2, + }, + label: { + show: true, + position: [10, -50], + formatter: () => { + return "{name1|待认证节点}"; + }, + rich: { + name1: { + color: "#FF6B01", + align: "center", + lineHeight: 35, + fontSize: 18, + fontWeight: 600, + padding: [11, 16.52, 11, 16.52], + backgroundColor: "rgba(63, 6, 3, 0.5)", + }, + }, + backgroundColor: "transparent", + }, + data: [ + { + name: "格陵兰", + value: pointA, + }, + ], + } as echarts.SeriesOption, + // 内层白色点 + { + type: "scatter", // 普通scatter,不带特效 + coordinateSystem: "geo", + zlevel: 4, // 确保在外层点上方 + color: "#FFFFFF", // 白色内层 + symbol: "circle", + symbolSize: 4, + label: { + show: false, + }, + data: [ + { + name: "格陵兰", + value: pointA, + }, + ], + } as echarts.SeriesOption + ); + // 添加B点 - 大型圆形区域 + series.push({ + type: "scatter", + coordinateSystem: "geo", + zlevel: 2, + color: "rgba(55, 255, 0, 0.50)", // 半透明绿色 + symbol: "circle", + symbolSize: 150, // 大尺寸圆形 + label: { + show: true, + position: [-70, -30], + formatter: () => { + return "{name2|权威节点团}"; + }, + rich: { + name2: { + color: "#37FF00", + align: "center", + lineHeight: 35, + fontSize: 18, + fontWeight: 600, + padding: [11, 16.52, 11, 16.52], + backgroundColor: "rgba(4, 59, 27, 0.5)", + }, + }, + backgroundColor: "transparent", + }, + data: [ + { + name: "加拿大", + value: pointB, + }, + ], + } as echarts.SeriesOption); + // 添加A到B的飞线(无特效) + series.push({ + type: "lines", + zlevel: 1, + effect: { + show: false, // 关闭特效 + }, + lineStyle: { + curveness: -0.4, // 飞线弧度 + type: "solid", + color: "#FEAA18", // 飞线颜色 + width: 1.5, + opacity: 0.8, + }, + data: [ + { + coords: [pointA, newPointB], // 从A点到B点 + }, + ], + } as echarts.SeriesOption); + // 计算飞线中点坐标(考虑曲率) + const x1 = pointA[0]; + const y1 = pointA[1]; + const x2 = newPointB[0]; + const y2 = newPointB[1]; + const curveness = -0.4; + // 计算控制点 + const cpx = (x1 + x2) / 2 - (y2 - y1) * curveness; + const cpy = (y1 + y2) / 2 - (x1 - x2) * curveness; + // 计算曲线上的中点 (t=0.5 时的贝塞尔曲线点) + const midX = x1 * 0.25 + cpx * 0.5 + x2 * 0.25; + const midY = y1 * 0.25 + cpy * 0.5 + y2 * 0.25; + // 将中点添加到 lineMidpointsRef 中,以便使用 DOM 方式创建标签 + lineMidpointsRef.current.push({ + id: "special-line-label", + midpoint: [midX, midY], + fromCountry: "A", + toCountry: "B", + }); + return series; + }; + const getOption = () => { + const series: echarts.SeriesOption[] = []; + getLianData(series); + // getMianLineTipData(series); // 添加主线tip 暂时隐藏 + if ( + tooltipType === "PASS_AUTHENTICATION" && + currentValue && + currentValue.length && + currentValue[0]?.authenticationPoint + ) { + console.log(currentValue, "values"); + + createSpecialPoints(series); // 添加特殊点和飞线 + createRipplePointsFromCoordinates( + currentValue[0]?.authenticationPoint || [], + series + ); + } + + const option = { + backgroundColor: "transparent", + // 全局提示框配置 + tooltip: { + show: true, + trigger: "item", + enterable: true, + confine: true, // 保持提示框在图表范围内 + appendToBody: true, // 将提示框附加到body以获得更好的定位 + // position: function(pos:any, params, dom, rect, size) { + position: function (pos: any) { + // 自定义定位逻辑(如果需要) + return [pos[0] + 10, pos[1] - 50]; // 从光标偏移 + }, + }, + // 底图样式 + geo: { + map: "world", // 地图类型 + roam: true, // 是否开启缩放 + zoom: 1, // 初始缩放大小 + // center: [11.3316626, 19.5845024], // 地图中心点 + layoutCenter: ["50%", "50%"], //地图位置 + scaleLimit: { + // 缩放等级 + min: 1, + max: 3, + }, + label: { + show: false, + }, + nameMap: countryNameMap, // 自定义地区的名称映射 + // 三维地理坐标系样式 + itemStyle: { + areaColor: "#020617", // 修改为要求的填充颜色 + borderColor: "#cbd5e1", // 修改为要求的边框颜色 + borderWidth: 1, // 边框宽度 + borderType: "dashed", // 修改为点线边框 + }, + emphasis: { + itemStyle: { + areaColor: "#172554", // 修改为鼠标悬停时的填充颜色 + borderColor: "#0ea5e9", // 修改为鼠标悬停时的边框颜色 + borderWidth: 1.2, // 修改为鼠标悬停时的边框宽度 + borderType: "solid", // 修改为实线边框 + }, + label: false, + }, + tooltip: { + show: true, + trigger: "item", + triggerOn: "click", // 提示框触发的条件 + enterable: true, // 鼠标是否可进入提示框浮层中,默认为false,如需详情内交互,如添加链接,按钮,可设置为 true + backgroundColor: "rgba(0,0,0,0.8)", + borderColor: "rgba(0,0,0,0.2)", + textStyle: { + color: "#fff", + }, + formatter: (parameters: { + name: string; + data: + | { + name: string; + datas: { tradingCountry: string }; + } + | undefined; + }) => { + if (parameters.data?.name) return parameters.data.name; + return parameters.name; + }, + }, + }, + series: series, + }; + return option; + }; + // 创建DOM标签 + const createDOMLabels = () => { + // 清除现有标签 + if (labelContainerRef.current) { + labelContainerRef.current.innerHTML = ""; + labelsRef.current?.forEach((item) => item?.remove()); + labelsRef.current = []; + } else { + // 创建标签容器 + const container = document.createElement("div"); + container.className = "line-labels-container"; + container.style.position = "absolute"; + container.style.top = "0"; + container.style.left = "0"; + container.style.pointerEvents = "none"; + container.style.zIndex = "1000"; + container.style.width = "100%"; + container.style.height = "100%"; + container.style.overflow = "hidden"; + // 添加到地图容器 + const chartDom = document.getElementById("screenGeo"); + if (chartDom) { + chartDom.style.position = "relative"; + chartDom.appendChild(container); + labelContainerRef.current = container; + } + } + // 创建新标签 + lineMidpointsRef.current.forEach((point, index) => { + const label = document.createElement("div"); + label.id = point.id; + label.className = "line-label"; + label.style.position = "absolute"; + label.style.textAlign = "center"; + label.style.transform = "translate(-50%, -50%)"; + label.style.whiteSpace = "nowrap"; + label.style.pointerEvents = "none"; + label.style.zIndex = "1001"; + // 特殊线标签(A到B的线) + if (point.id === "special-line-label") { + label.style.backgroundColor = "#8B3700"; + label.style.color = "#FFB27A"; + label.style.padding = "5px 10px"; + label.style.borderRadius = "4px"; + label.style.fontSize = "18px"; + label.style.fontWeight = "normal"; + label.textContent = "SS签名"; + } + // 其他线标签 + else { + label.style.backgroundColor = "#8B3700"; + label.style.color = "#FFB27A"; + label.style.padding = "5px 10px"; + label.style.borderRadius = "4px"; + label.style.fontSize = "18px"; + label.style.fontWeight = "normal"; + label.textContent = "SS签名"; + } + // 添加到容器 + labelContainerRef.current?.appendChild(label); + labelsRef.current.push(label); + }); + // 更新标签位置 + updateLabelPositions(); + }; + // 更新标签位置 + const updateLabelPositions = () => { + if (!proxyGeoRef.current || !labelContainerRef.current) return; + lineMidpointsRef.current.forEach((point, index) => { + const label = labelsRef.current[index]; + if (!label) return; + const pixelPoint = proxyGeoRef.current?.convertToPixel( + "geo", + point.midpoint + ); + if (pixelPoint && Array.isArray(pixelPoint)) { + label.style.left = `${pixelPoint[0]}px`; + label.style.top = `${pixelPoint[1]}px`; + } + }); + }; + const handleResize = () => { + proxyGeoRef.current?.resize(); + updateLabelPositions(); + }; + useEffect(() => { + preMainToData.current?.some( + (item, index) => item.country_code !== mainToData[index]?.country_code + ) && proxyGeoRef.current?.clear(); + preMainToData.current = mainToData; + const option = getOption(); + proxyGeoRef.current?.setOption(option); + // 创建DOM标签 + setTimeout(createDOMLabels, 100); + }, [newHomeProxies, mainToData]); + useEffect(() => { + const chartDom = document.getElementById("screenGeo"); + proxyGeoRef.current = echarts.init(chartDom); + echarts.registerMap( + "world", + worldGeoJson as unknown as Parameters[1] + ); + const option = getOption(); + option && proxyGeoRef.current?.setOption(option); + // 添加地图交互事件监听器 + proxyGeoRef.current?.on("georoam", updateLabelPositions); + // 页面resize时触发 + window.addEventListener("resize", handleResize); + return () => { + window.removeEventListener("resize", handleResize); + proxyGeoRef.current?.off("georoam", updateLabelPositions); + // 清理DOM标签 + if (labelContainerRef.current) { + labelContainerRef.current.remove(); + labelContainerRef.current = null; + labelsRef.current = []; + } + proxyGeoRef.current?.dispose(); + proxyGeoRef.current = null; + }; + }, []); + useEffect(() => { + if (tooltipType !== "PASS_AUTHENTICATION") { + lineMidpointsRef.current = []; + } + if (tooltipClosed) { + if (tooltipType === "NESTED_ENCRYPTION") { + createCustomTooltip(); + } + if (tooltipType === "TRAFFIC_OBFUSCATION") { + createCustomTooltip2(); + } + } else { + customTooltipRef.current?.remove(); + customTooltip2Ref.current?.remove(); + customTooltipRef.current = null; + customTooltip2Ref.current = null; + } + return () => { + customTooltipRef.current?.remove(); + customTooltip2Ref.current?.remove(); + customTooltipRef.current = null; + customTooltip2Ref.current = null; + }; + }, [tooltipClosed, tooltipType, currentValue]); + return ( +
+
+
+ ); + } +); diff --git a/src/pages/anti-dark-analysis-network/data/index.ts b/src/pages/anti-dark-analysis-network/data/index.ts new file mode 100644 index 0000000..db909b9 --- /dev/null +++ b/src/pages/anti-dark-analysis-network/data/index.ts @@ -0,0 +1,1175 @@ +export const screenData = { + path_list: [ + { + account: "admin", + account_is_admin: true, + exclusive: "none", + name: "default(10.66.66.234)-c250", + proxies: ["意大利-米兰-312", "南苏丹-朱巴-374"], + proxies_code: ["it", "ss"], + use: true, + current_ip_use: false, + }, + ], + + proxy_info: { + exclusive: "", + name: "default(10.66.66.234)-c250", + wg: false, + change_time: 0, + change_at: 0, + proxies: [ + { + country_code: "it", + ingress_country_code: "pl", + }, + { + country_code: "ss", + ingress_country_code: "tr", + }, + ], + }, +}; + +export const nodeList = [ + { + name: "印度尼西亚-雅加达-123", + server: "146.190.107.*", + port: 4565, + protocol: "ss", + country_name: "Indonesia", + country_code: "id", + city_name: "Jakarta", + delay: 402, + use_status: "none", + ingress_ip: "146.190.107.*", + ip: "129.227.46.162", + ingress_country_name: "Singapore", + ingress_country_code: "sg", + ingress_city_name: "Singapore", + type: "guard", + source: "platform", + vulnerability_type: "", + }, + { + name: "印度尼西亚-雅加达-195", + server: "103.106.229.*", + port: 4565, + protocol: "ss", + country_name: "Indonesia", + country_code: "id", + city_name: "Jakarta", + delay: 410, + use_status: "none", + ingress_ip: "103.106.229.*", + ip: "129.227.46.162", + ingress_country_name: "Singapore", + ingress_country_code: "sg", + ingress_city_name: "Singapore", + type: "guard", + source: "platform", + vulnerability_type: "", + }, + { + name: "印度尼西亚-雅加达-23", + server: "165.22.105.*", + port: 4565, + protocol: "ss", + country_name: "Indonesia", + country_code: "id", + city_name: "Jakarta", + delay: 385, + use_status: "none", + ingress_ip: "165.22.105.*", + ip: "129.227.46.130", + ingress_country_name: "Singapore", + ingress_country_code: "sg", + ingress_city_name: "Singapore", + type: "guard", + source: "platform", + vulnerability_type: "", + }, + { + name: "德国-柏林-309", + server: "92.118.205.*", + port: 4564, + protocol: "ss", + country_name: "Germany", + country_code: "de", + city_name: "Berlin", + delay: 435, + use_status: "none", + ingress_ip: "92.118.205.*", + ip: "193.32.248.70", + ingress_country_name: "Poland", + ingress_country_code: "pl", + ingress_city_name: "Warsaw", + type: "guard", + source: "platform", + vulnerability_type: "", + }, + { + name: "德国-柏林-319", + server: "92.118.205.*", + port: 4564, + protocol: "ss", + country_name: "Germany", + country_code: "de", + city_name: "Berlin", + delay: 431, + use_status: "none", + ingress_ip: "92.118.205.*", + ip: "193.32.248.69", + ingress_country_name: "Poland", + ingress_country_code: "pl", + ingress_city_name: "Warsaw", + type: "guard", + source: "platform", + vulnerability_type: "", + }, + { + name: "德国-柏林-329", + server: "92.118.205.*", + port: 4564, + protocol: "ss", + country_name: "Germany", + country_code: "de", + city_name: "Berlin", + delay: 445, + use_status: "none", + ingress_ip: "92.118.205.*", + ip: "193.32.248.75", + ingress_country_name: "Poland", + ingress_country_code: "pl", + ingress_city_name: "Warsaw", + type: "guard", + source: "platform", + vulnerability_type: "", + }, + { + name: "意大利-米兰-312", + server: "92.118.205.*", + port: 4567, + protocol: "ss", + country_name: "Italy", + country_code: "it", + city_name: "Milan", + delay: 413, + use_status: "use", + ingress_ip: "92.118.205.*", + ip: "178.249.211.92", + ingress_country_name: "Poland", + ingress_country_code: "pl", + ingress_city_name: "Warsaw", + type: "guard", + source: "platform", + vulnerability_type: "", + }, + { + name: "意大利-米兰-322", + server: "92.118.205.*", + port: 4567, + protocol: "ss", + country_name: "Italy", + country_code: "it", + city_name: "Milan", + delay: 423, + use_status: "none", + ingress_ip: "92.118.205.*", + ip: "178.249.211.79", + ingress_country_name: "Poland", + ingress_country_code: "pl", + ingress_city_name: "Warsaw", + type: "guard", + source: "platform", + vulnerability_type: "", + }, + { + name: "意大利-米兰-332", + server: "92.118.205.*", + port: 4567, + protocol: "ss", + country_name: "Italy", + country_code: "it", + city_name: "Milan", + delay: 440, + use_status: "none", + ingress_ip: "92.118.205.*", + ip: "146.70.225.2", + ingress_country_name: "Poland", + ingress_country_code: "pl", + ingress_city_name: "Warsaw", + type: "guard", + source: "platform", + vulnerability_type: "", + }, + { + name: "新加坡-118", + server: "146.190.107.*", + port: 4560, + protocol: "ss", + country_name: "Singapore", + country_code: "sg", + city_name: "Singapore", + delay: 373, + use_status: "none", + ingress_ip: "146.190.107.*", + ip: "138.199.60.15", + ingress_country_name: "Singapore", + ingress_country_code: "sg", + ingress_city_name: "Singapore", + type: "guard", + source: "platform", + vulnerability_type: "", + }, + { + name: "新加坡-18", + server: "165.22.105.*", + port: 4560, + protocol: "ss", + country_name: "Singapore", + country_code: "sg", + city_name: "Singapore", + delay: 374, + use_status: "none", + ingress_ip: "165.22.105.*", + ip: "138.199.60.2", + ingress_country_name: "Singapore", + ingress_country_code: "sg", + ingress_city_name: "Singapore", + type: "guard", + source: "platform", + vulnerability_type: "", + }, + { + name: "新加坡-190", + server: "103.106.229.*", + port: 4560, + protocol: "ss", + country_name: "Singapore", + country_code: "sg", + city_name: "Singapore", + delay: 379, + use_status: "none", + ingress_ip: "103.106.229.*", + ip: "146.70.199.130", + ingress_country_name: "Singapore", + ingress_country_code: "sg", + ingress_city_name: "Singapore", + type: "guard", + source: "platform", + vulnerability_type: "", + }, + { + name: "新西兰-奥克兰-125", + server: "146.190.107.*", + port: 4567, + protocol: "ss", + country_name: "NewZealand", + country_code: "nz", + city_name: "Auckland", + delay: 507, + use_status: "none", + ingress_ip: "146.190.107.*", + ip: "103.75.11.66", + ingress_country_name: "Singapore", + ingress_country_code: "sg", + ingress_city_name: "Singapore", + type: "guard", + source: "platform", + vulnerability_type: "", + }, + { + name: "新西兰-奥克兰-197", + server: "103.106.229.*", + port: 4567, + protocol: "ss", + country_name: "NewZealand", + country_code: "nz", + city_name: "Auckland", + delay: 502, + use_status: "none", + ingress_ip: "103.106.229.*", + ip: "103.75.11.66", + ingress_country_name: "Singapore", + ingress_country_code: "sg", + ingress_city_name: "Singapore", + type: "guard", + source: "platform", + vulnerability_type: "", + }, + { + name: "新西兰-奥克兰-25", + server: "165.22.105.*", + port: 4567, + protocol: "ss", + country_name: "NewZealand", + country_code: "nz", + city_name: "Auckland", + delay: 504, + use_status: "none", + ingress_ip: "165.22.105.*", + ip: "103.75.11.50", + ingress_country_name: "Singapore", + ingress_country_code: "sg", + ingress_city_name: "Singapore", + type: "guard", + source: "platform", + vulnerability_type: "", + }, + { + name: "日本-东京-121", + server: "146.190.107.*", + port: 4563, + protocol: "ss", + country_name: "Japan", + country_code: "jp", + city_name: "Tokyo", + delay: 440, + use_status: "none", + ingress_ip: "146.190.107.*", + ip: "138.199.21.226", + ingress_country_name: "Singapore", + ingress_country_code: "sg", + ingress_city_name: "Singapore", + type: "guard", + source: "platform", + vulnerability_type: "", + }, + { + name: "日本-东京-193", + server: "103.106.229.*", + port: 4563, + protocol: "ss", + country_name: "Japan", + country_code: "jp", + city_name: "Tokyo", + delay: 442, + use_status: "none", + ingress_ip: "103.106.229.*", + ip: "146.70.201.66", + ingress_country_name: "Singapore", + ingress_country_code: "sg", + ingress_city_name: "Singapore", + type: "guard", + source: "platform", + vulnerability_type: "", + }, + { + name: "日本-东京-21", + server: "165.22.105.*", + port: 4563, + protocol: "ss", + country_name: "Japan", + country_code: "jp", + city_name: "Tokyo", + delay: 463, + use_status: "none", + ingress_ip: "165.22.105.*", + ip: "138.199.21.239", + ingress_country_name: "Singapore", + ingress_country_code: "sg", + ingress_city_name: "Singapore", + type: "guard", + source: "platform", + vulnerability_type: "", + }, + { + name: "日本-大阪-120", + server: "146.190.107.*", + port: 4562, + protocol: "ss", + country_name: "Japan", + country_code: "jp", + city_name: "Osaka", + delay: 470, + use_status: "none", + ingress_ip: "146.190.107.*", + ip: "194.114.136.34", + ingress_country_name: "Singapore", + ingress_country_code: "sg", + ingress_city_name: "Singapore", + type: "guard", + source: "platform", + vulnerability_type: "", + }, + { + name: "日本-大阪-192", + server: "103.106.229.*", + port: 4562, + protocol: "ss", + country_name: "Japan", + country_code: "jp", + city_name: "Osaka", + delay: 552, + use_status: "none", + ingress_ip: "103.106.229.*", + ip: "194.114.136.96", + ingress_country_name: "Singapore", + ingress_country_code: "sg", + ingress_city_name: "Singapore", + type: "guard", + source: "platform", + vulnerability_type: "", + }, + { + name: "日本-大阪-20", + server: "165.22.105.*", + port: 4562, + protocol: "ss", + country_name: "Japan", + country_code: "jp", + city_name: "Osaka", + delay: 458, + use_status: "none", + ingress_ip: "165.22.105.*", + ip: "194.114.136.3", + ingress_country_name: "Singapore", + ingress_country_code: "sg", + ingress_city_name: "Singapore", + type: "guard", + source: "platform", + vulnerability_type: "", + }, + { + name: "法国-巴黎-308", + server: "92.118.205.*", + port: 4563, + protocol: "ss", + country_name: "France", + country_code: "fr", + city_name: "Paris", + delay: 433, + use_status: "none", + ingress_ip: "92.118.205.*", + ip: "193.32.126.70", + ingress_country_name: "Poland", + ingress_country_code: "pl", + ingress_city_name: "Warsaw", + type: "guard", + source: "platform", + vulnerability_type: "", + }, + { + name: "法国-巴黎-318", + server: "92.118.205.*", + port: 4563, + protocol: "ss", + country_name: "France", + country_code: "fr", + city_name: "Paris", + delay: 442, + use_status: "none", + ingress_ip: "92.118.205.*", + ip: "193.32.126.67", + ingress_country_name: "Poland", + ingress_country_code: "pl", + ingress_city_name: "Warsaw", + type: "guard", + source: "platform", + vulnerability_type: "", + }, + { + name: "法国-巴黎-328", + server: "92.118.205.*", + port: 4563, + protocol: "ss", + country_name: "France", + country_code: "fr", + city_name: "Paris", + delay: 422, + use_status: "none", + ingress_ip: "92.118.205.*", + ip: "146.70.184.66", + ingress_country_name: "Poland", + ingress_country_code: "pl", + ingress_city_name: "Warsaw", + type: "guard", + source: "platform", + vulnerability_type: "", + }, + { + name: "波兰-华沙-310", + server: "92.118.205.*", + port: 4565, + protocol: "ss", + country_name: "Poland", + country_code: "pl", + city_name: "Warsaw", + delay: 413, + use_status: "none", + ingress_ip: "92.118.205.*", + ip: "45.128.38.226", + ingress_country_name: "Poland", + ingress_country_code: "pl", + ingress_city_name: "Warsaw", + type: "guard", + source: "platform", + vulnerability_type: "", + }, + { + name: "波兰-华沙-320", + server: "92.118.205.*", + port: 4565, + protocol: "ss", + country_name: "Poland", + country_code: "pl", + city_name: "Warsaw", + delay: -1, + use_status: "none", + ingress_ip: "92.118.205.*", + ip: "45.134.212.79", + ingress_country_name: "Poland", + ingress_country_code: "pl", + ingress_city_name: "Warsaw", + type: "guard", + source: "platform", + vulnerability_type: "", + }, + { + name: "波兰-华沙-330", + server: "92.118.205.*", + port: 4565, + protocol: "ss", + country_name: "Poland", + country_code: "pl", + city_name: "Warsaw", + delay: 411, + use_status: "none", + ingress_ip: "92.118.205.*", + ip: "146.70.144.34", + ingress_country_name: "Poland", + ingress_country_code: "pl", + ingress_city_name: "Warsaw", + type: "guard", + source: "platform", + vulnerability_type: "", + }, + { + name: "泰国-曼谷-122", + server: "146.190.107.*", + port: 4564, + protocol: "ss", + country_name: "Thailand", + country_code: "th", + city_name: "Bangkok", + delay: 411, + use_status: "none", + ingress_ip: "146.190.107.*", + ip: "156.59.50.226", + ingress_country_name: "Singapore", + ingress_country_code: "sg", + ingress_city_name: "Singapore", + type: "guard", + source: "platform", + vulnerability_type: "", + }, + { + name: "泰国-曼谷-194", + server: "103.106.229.*", + port: 4564, + protocol: "ss", + country_name: "Thailand", + country_code: "th", + city_name: "Bangkok", + delay: 411, + use_status: "none", + ingress_ip: "103.106.229.*", + ip: "156.59.50.226", + ingress_country_name: "Singapore", + ingress_country_code: "sg", + ingress_city_name: "Singapore", + type: "guard", + source: "platform", + vulnerability_type: "", + }, + { + name: "泰国-曼谷-22", + server: "165.22.105.*", + port: 4564, + protocol: "ss", + country_name: "Thailand", + country_code: "th", + city_name: "Bangkok", + delay: 402, + use_status: "none", + ingress_ip: "165.22.105.*", + ip: "156.59.50.194", + ingress_country_name: "Singapore", + ingress_country_code: "sg", + ingress_city_name: "Singapore", + type: "guard", + source: "platform", + vulnerability_type: "", + }, + { + name: "澳大利亚-悉尼-124", + server: "146.190.107.*", + port: 4566, + protocol: "ss", + country_name: "Australia", + country_code: "au", + city_name: "Sydney", + delay: 468, + use_status: "none", + ingress_ip: "146.190.107.*", + ip: "146.70.141.194", + ingress_country_name: "Singapore", + ingress_country_code: "sg", + ingress_city_name: "Singapore", + type: "guard", + source: "platform", + vulnerability_type: "", + }, + { + name: "澳大利亚-悉尼-196", + server: "103.106.229.*", + port: 4566, + protocol: "ss", + country_name: "Australia", + country_code: "au", + city_name: "Sydney", + delay: 469, + use_status: "none", + ingress_ip: "103.106.229.*", + ip: "103.136.147.65", + ingress_country_name: "Singapore", + ingress_country_code: "sg", + ingress_city_name: "Singapore", + type: "guard", + source: "platform", + vulnerability_type: "", + }, + { + name: "澳大利亚-悉尼-24", + server: "165.22.105.*", + port: 4566, + protocol: "ss", + country_name: "Australia", + country_code: "au", + city_name: "Sydney", + delay: 468, + use_status: "none", + ingress_ip: "165.22.105.*", + ip: "146.70.200.2", + ingress_country_name: "Singapore", + ingress_country_code: "sg", + ingress_city_name: "Singapore", + type: "guard", + source: "platform", + vulnerability_type: "", + }, + { + name: "瑞典-斯德哥尔摩-305", + server: "92.118.205.*", + port: 4560, + protocol: "ss", + country_name: "Sweden", + country_code: "se", + city_name: "Stockholm", + delay: 446, + use_status: "none", + ingress_ip: "92.118.205.*", + ip: "185.65.135.68", + ingress_country_name: "Poland", + ingress_country_code: "pl", + ingress_city_name: "Warsaw", + type: "guard", + source: "platform", + vulnerability_type: "", + }, + { + name: "瑞典-斯德哥尔摩-315", + server: "92.118.205.*", + port: 4560, + protocol: "ss", + country_name: "Sweden", + country_code: "se", + city_name: "Stockholm", + delay: 432, + use_status: "none", + ingress_ip: "92.118.205.*", + ip: "185.195.233.76", + ingress_country_name: "Poland", + ingress_country_code: "pl", + ingress_city_name: "Warsaw", + type: "guard", + source: "platform", + vulnerability_type: "", + }, + { + name: "瑞典-斯德哥尔摩-325", + server: "92.118.205.*", + port: 4560, + protocol: "ss", + country_name: "Sweden", + country_code: "se", + city_name: "Stockholm", + delay: -1, + use_status: "none", + ingress_ip: "92.118.205.*", + ip: "185.195.233.71", + ingress_country_name: "Poland", + ingress_country_code: "pl", + ingress_city_name: "Warsaw", + type: "guard", + source: "platform", + vulnerability_type: "", + }, + { + name: "瑞士-苏黎世-306", + server: "92.118.205.*", + port: 4561, + protocol: "ss", + country_name: "Switzerland", + country_code: "ch", + city_name: "Zurich", + delay: 432, + use_status: "none", + ingress_ip: "92.118.205.*", + ip: "193.32.127.70", + ingress_country_name: "Poland", + ingress_country_code: "pl", + ingress_city_name: "Warsaw", + type: "guard", + source: "platform", + vulnerability_type: "", + }, + { + name: "瑞士-苏黎世-316", + server: "92.118.205.*", + port: 4561, + protocol: "ss", + country_name: "Switzerland", + country_code: "ch", + city_name: "Zurich", + delay: 430, + use_status: "none", + ingress_ip: "92.118.205.*", + ip: "193.32.127.68", + ingress_country_name: "Poland", + ingress_country_code: "pl", + ingress_city_name: "Warsaw", + type: "guard", + source: "platform", + vulnerability_type: "", + }, + { + name: "瑞士-苏黎世-326", + server: "92.118.205.*", + port: 4561, + protocol: "ss", + country_name: "Switzerland", + country_code: "ch", + city_name: "Zurich", + delay: 419, + use_status: "none", + ingress_ip: "92.118.205.*", + ip: "146.70.134.98", + ingress_country_name: "Poland", + ingress_country_code: "pl", + ingress_city_name: "Warsaw", + type: "guard", + source: "platform", + vulnerability_type: "", + }, + { + name: "美国-洛杉矶-126", + server: "146.190.107.*", + port: 4568, + protocol: "ss", + country_name: "USA", + country_code: "us", + city_name: "LosAngeles", + delay: 552, + use_status: "none", + ingress_ip: "146.190.107.*", + ip: "169.150.203.2", + ingress_country_name: "Singapore", + ingress_country_code: "sg", + ingress_city_name: "Singapore", + type: "guard", + source: "platform", + vulnerability_type: "", + }, + { + name: "美国-洛杉矶-198", + server: "103.106.229.*", + port: 4568, + protocol: "ss", + country_name: "USA", + country_code: "us", + city_name: "LosAngeles", + delay: 558, + use_status: "none", + ingress_ip: "103.106.229.*", + ip: "23.162.40.86", + ingress_country_name: "Singapore", + ingress_country_code: "sg", + ingress_city_name: "Singapore", + type: "guard", + source: "platform", + vulnerability_type: "", + }, + { + name: "美国-洛杉矶-26", + server: "165.22.105.*", + port: 4568, + protocol: "ss", + country_name: "USA", + country_code: "us", + city_name: "LosAngeles", + delay: 548, + use_status: "none", + ingress_ip: "165.22.105.*", + ip: "198.44.129.98", + ingress_country_name: "Singapore", + ingress_country_code: "sg", + ingress_city_name: "Singapore", + type: "guard", + source: "platform", + vulnerability_type: "", + }, + { + name: "美国-达拉斯-127", + server: "146.190.107.*", + port: 4569, + protocol: "ss", + country_name: "USA", + country_code: "us", + city_name: "Dallas", + delay: 591, + use_status: "none", + ingress_ip: "146.190.107.*", + ip: "174.127.113.8", + ingress_country_name: "Singapore", + ingress_country_code: "sg", + ingress_city_name: "Singapore", + type: "guard", + source: "platform", + vulnerability_type: "", + }, + { + name: "美国-达拉斯-199", + server: "103.106.229.*", + port: 4569, + protocol: "ss", + country_name: "USA", + country_code: "us", + city_name: "Dallas", + delay: 581, + use_status: "none", + ingress_ip: "103.106.229.*", + ip: "206.217.206.107", + ingress_country_name: "Singapore", + ingress_country_code: "sg", + ingress_city_name: "Singapore", + type: "guard", + source: "platform", + vulnerability_type: "", + }, + { + name: "美国-达拉斯-27", + server: "165.22.105.*", + port: 4569, + protocol: "ss", + country_name: "USA", + country_code: "us", + city_name: "Dallas", + delay: 604, + use_status: "none", + ingress_ip: "165.22.105.*", + ip: "146.70.211.66", + ingress_country_name: "Singapore", + ingress_country_code: "sg", + ingress_city_name: "Singapore", + type: "guard", + source: "platform", + vulnerability_type: "", + }, + { + name: "芬兰-赫尔辛基-311", + server: "92.118.205.*", + port: 4566, + protocol: "ss", + country_name: "Finland", + country_code: "fi", + city_name: "Helsinki", + delay: 421, + use_status: "none", + ingress_ip: "92.118.205.*", + ip: "185.204.1.219", + ingress_country_name: "Poland", + ingress_country_code: "pl", + ingress_city_name: "Warsaw", + type: "guard", + source: "platform", + vulnerability_type: "", + }, + { + name: "芬兰-赫尔辛基-321", + server: "92.118.205.*", + port: 4566, + protocol: "ss", + country_name: "Finland", + country_code: "fi", + city_name: "Helsinki", + delay: 419, + use_status: "none", + ingress_ip: "92.118.205.*", + ip: "185.204.1.211", + ingress_country_name: "Poland", + ingress_country_code: "pl", + ingress_city_name: "Warsaw", + type: "guard", + source: "platform", + vulnerability_type: "", + }, + { + name: "芬兰-赫尔辛基-331", + server: "92.118.205.*", + port: 4566, + protocol: "ss", + country_name: "Finland", + country_code: "fi", + city_name: "Helsinki", + delay: 430, + use_status: "none", + ingress_ip: "92.118.205.*", + ip: "193.138.7.177", + ingress_country_name: "Poland", + ingress_country_code: "pl", + ingress_city_name: "Warsaw", + type: "guard", + source: "platform", + vulnerability_type: "", + }, + { + name: "英国-伦敦-307", + server: "92.118.205.*", + port: 4562, + protocol: "ss", + country_name: "UnitedKingdom", + country_code: "gb", + city_name: "London", + delay: 432, + use_status: "none", + ingress_ip: "92.118.205.*", + ip: "185.195.232.67", + ingress_country_name: "Poland", + ingress_country_code: "pl", + ingress_city_name: "Warsaw", + type: "guard", + source: "platform", + vulnerability_type: "", + }, + { + name: "英国-伦敦-317", + server: "92.118.205.*", + port: 4562, + protocol: "ss", + country_name: "UnitedKingdom", + country_code: "gb", + city_name: "London", + delay: 442, + use_status: "none", + ingress_ip: "92.118.205.*", + ip: "141.98.252.222", + ingress_country_name: "Poland", + ingress_country_code: "pl", + ingress_city_name: "Warsaw", + type: "guard", + source: "platform", + vulnerability_type: "", + }, + { + name: "英国-伦敦-327", + server: "92.118.205.*", + port: 4562, + protocol: "ss", + country_name: "UnitedKingdom", + country_code: "gb", + city_name: "London", + delay: 431, + use_status: "none", + ingress_ip: "92.118.205.*", + ip: "185.248.85.18", + ingress_country_name: "Poland", + ingress_country_code: "pl", + ingress_city_name: "Warsaw", + type: "guard", + source: "platform", + vulnerability_type: "", + }, + { + name: "荷兰-阿姆斯特丹-314", + server: "92.118.205.*", + port: 4569, + protocol: "ss", + country_name: "Netherlands", + country_code: "nl", + city_name: "Amsterdam", + delay: 434, + use_status: "none", + ingress_ip: "92.118.205.*", + ip: "193.32.249.70", + ingress_country_name: "Poland", + ingress_country_code: "pl", + ingress_city_name: "Warsaw", + type: "guard", + source: "platform", + vulnerability_type: "", + }, + { + name: "荷兰-阿姆斯特丹-324", + server: "92.118.205.*", + port: 4569, + protocol: "ss", + country_name: "Netherlands", + country_code: "nl", + city_name: "Amsterdam", + delay: 435, + use_status: "none", + ingress_ip: "92.118.205.*", + ip: "185.65.134.82", + ingress_country_name: "Poland", + ingress_country_code: "pl", + ingress_city_name: "Warsaw", + type: "guard", + source: "platform", + vulnerability_type: "", + }, + { + name: "荷兰-阿姆斯特丹-334", + server: "92.118.205.*", + port: 4569, + protocol: "ss", + country_name: "Netherlands", + country_code: "nl", + city_name: "Amsterdam", + delay: 419, + use_status: "none", + ingress_ip: "92.118.205.*", + ip: "169.150.196.15", + ingress_country_name: "Poland", + ingress_country_code: "pl", + ingress_city_name: "Warsaw", + type: "guard", + source: "platform", + vulnerability_type: "", + }, + { + name: "西班牙-马德里-313", + server: "92.118.205.*", + port: 4568, + protocol: "ss", + country_name: "Spain", + country_code: "es", + city_name: "Madrid", + delay: 432, + use_status: "none", + ingress_ip: "92.118.205.*", + ip: "146.70.128.194", + ingress_country_name: "Poland", + ingress_country_code: "pl", + ingress_city_name: "Warsaw", + type: "guard", + source: "platform", + vulnerability_type: "", + }, + { + name: "西班牙-马德里-323", + server: "92.118.205.*", + port: 4568, + protocol: "ss", + country_name: "Spain", + country_code: "es", + city_name: "Madrid", + delay: 432, + use_status: "none", + ingress_ip: "92.118.205.*", + ip: "45.134.213.207", + ingress_country_name: "Poland", + ingress_country_code: "pl", + ingress_city_name: "Warsaw", + type: "guard", + source: "platform", + vulnerability_type: "", + }, + { + name: "西班牙-马德里-333", + server: "92.118.205.*", + port: 4568, + protocol: "ss", + country_name: "Spain", + country_code: "es", + city_name: "Madrid", + delay: 447, + use_status: "none", + ingress_ip: "92.118.205.*", + ip: "146.70.128.226", + ingress_country_name: "Poland", + ingress_country_code: "pl", + ingress_city_name: "Warsaw", + type: "guard", + source: "platform", + vulnerability_type: "", + }, + { + name: "香港-119", + server: "146.190.107.*", + port: 4561, + protocol: "ss", + country_name: "HongKong", + country_code: "hk", + city_name: "HongKong", + delay: 408, + use_status: "none", + ingress_ip: "146.190.107.*", + ip: "103.125.233.3", + ingress_country_name: "Singapore", + ingress_country_code: "sg", + ingress_city_name: "Singapore", + type: "guard", + source: "platform", + vulnerability_type: "", + }, + { + name: "香港-19", + server: "165.22.105.*", + port: 4561, + protocol: "ss", + country_name: "HongKong", + country_code: "hk", + city_name: "HongKong", + delay: 426, + use_status: "none", + ingress_ip: "165.22.105.*", + ip: "103.125.233.18", + ingress_country_name: "Singapore", + ingress_country_code: "sg", + ingress_city_name: "Singapore", + type: "guard", + source: "platform", + vulnerability_type: "", + }, + { + name: "香港-191", + server: "103.106.229.*", + port: 4561, + protocol: "ss", + country_name: "HongKong", + country_code: "hk", + city_name: "HongKong", + delay: 419, + use_status: "none", + ingress_ip: "103.106.229.*", + ip: "146.70.224.2", + ingress_country_name: "Singapore", + ingress_country_code: "sg", + ingress_city_name: "Singapore", + type: "guard", + source: "platform", + vulnerability_type: "", + }, +]; diff --git a/src/pages/anti-dark-analysis-network/data/mockData.ts b/src/pages/anti-dark-analysis-network/data/mockData.ts new file mode 100644 index 0000000..f1c8dc2 --- /dev/null +++ b/src/pages/anti-dark-analysis-network/data/mockData.ts @@ -0,0 +1,449 @@ + +export const TRAFFIC_OBFUSCATION = { + type: "NESTED_ENCRYPTION", + name: "流量混淆", + code: "ZA", + 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: "通信认证", + startPoint: "GL", + endPoint: "CA", + 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, +} diff --git a/src/pages/anti-dark-analysis-network/index.scss b/src/pages/anti-dark-analysis-network/index.scss new file mode 100644 index 0000000..ad5969c --- /dev/null +++ b/src/pages/anti-dark-analysis-network/index.scss @@ -0,0 +1,211 @@ +// // 添加到 index.scss +.decentralized { + background-color: #0f172a; + + // background-image: linear-gradient(180deg, #172554 0%, #0A0F2A 100%); + .box { + width: 100%; + height: 100%; + background-image: url("@/assets/image/line-bg.png"); + background-size: cover; + background-repeat: no-repeat; + background-position: center; + position: absolute; + left: 0; + top: 0; + mix-blend-mode: lighten; + } + + .web3-line::after { + content: ""; + width: 5px; + height: 5px; + border-radius: 50%; + background-color: #7D82FF; + position: absolute; + left: 50%; + top: 0px; + z-index: 999; + transform: translate(-50%, 0); + } +} + + +// // 轮播容器样式 +.carousel-container { + display: flex; + align-items: center; + gap: 3rem; // 对应原来的gap-12 + // width: 100%; + +} + +.bt1 { + display: flex; + padding: var(--8-spacing-04, 8px) var(--16-spacing-08, 16px); + justify-content: center; + align-items: center; + gap: var(--8-spacing-04, 8px); + border-radius: var(--radius-6, 6px); + border: 1px solid var(--Colors-Bluepurple-600, #4136F5); + background: var(--button-wireframe-button-wireframe, rgba(9, 9, 11, 0.00)); + box-shadow: 0px 0px 4px 0px var(--Colors-Bluepurple-500, #5457FF), 0px 0px 10px 0px var(--Colors-Bluepurple-600, #4136F5); + color: var(--text-text-primary-900, #FFF); + /* Text/Medium/T5文本1 */ + font-family: "PingFang SC"; + font-size: 14px; + font-style: normal; + font-weight: 500; + // line-height: 24px; + /* 171.429% */ +} + + + +.bt2 { + + display: flex; + padding: var(--8-spacing-04, 8px) var(--16-spacing-08, 16px); + justify-content: center; + align-items: center; + gap: var(--8-spacing-04, 8px); + border-radius: var(--radius-6, 6px); + border: 1px solid var(--Colors-Rose-600, #E11D48); + background: var(--button-wireframe-button-wireframe, rgba(255, 255, 255, 0.00)); + box-shadow: 0px 0px 4px 0px var(--Colors-Rose-600, #E11D48), 0px 0px 10px 0px var(--Colors-Rose-600, #E11D48); + color: var(--text-text-primary-900, #FFF); + + font-family: "PingFang SC"; + font-size: 14px; + font-style: normal; + font-weight: 500; + // line-height: 24px; +} + +.tip-box { + position: relative; + width: 626px; + height: 281px; + padding: 20.85px 20.353px; + background: rgba(0, 11.82, 33.10, 0.10); + border-radius: 8px; + outline: 0.46px solid white; + outline-offset: -0.46px; + backdrop-filter: blur(5.50px); + + .close-icon , .close-icon2 { + width: 16px; + height: 16px; + position: absolute; + top: 20px; + right: 20px; + color: #FFF; + } + + .label { + width: 100%; + color: #FFF; + font-size: 18px; + font-weight: 600; + line-height: 16px; + } + + .encryption-img { + width: 526px; + height: 241px; + margin-left: 16px; + } + + .traffic-obfuscation-img{ + width: 597px; + height: 241px; + margin-left: 16px; + } +} + +.tooltip-content { + position: relative; + display: flex; + + .line-img { + width: 312.221px; + } + + .line-img-left{ + width: 216.86px; + margin-top: 30px; + } + + .fill { + width: 9.165px; + height: 9.165px; + border-radius: 50%; + background-color: #18E4FF; + position: absolute; + left: 307.5px; + top: 77.5px; + z-index: 99; + } + + .fill-left { + width: 9.165px; + height: 9.165px; + border-radius: 50%; + background-color: #18E4FF; + position: absolute; + right:210.5px; + top: 82.5px; + z-index: 99; + } +} + + +// // 轮播项目 +// .carousel-item { +// flex: 0 0 auto; +// } + +// // View Transitions 自定义样式 +// @keyframes slide-from-right { +// from { +// transform: translateX(40px); +// opacity: 0; +// } +// } + +// @keyframes slide-to-left { +// to { +// transform: translateX(-40px); +// opacity: 0; +// } +// } + +// @keyframes slide-from-left { +// from { +// transform: translateX(-40px); +// opacity: 0; +// } +// } + +// @keyframes slide-to-right { +// to { +// transform: translateX(40px); +// opacity: 0; +// } +// } + +// // 自定义 View Transitions 动画 +// ::view-transition-old(web3-item-1-4), +// ::view-transition-old(web3-item-2-4) { +// animation: 0.8s slide-to-left ease-in-out; +// } + +// ::view-transition-new(web3-item-1-0), +// ::view-transition-new(web3-item-2-0) { +// animation: 0.8s slide-from-left ease-in-out; +// } + +// // 确保过渡期间元素可见 +// ::view-transition-group(*) { +// animation-duration: 0.8s; +// } \ No newline at end of file diff --git a/src/pages/anti-dark-analysis-network/index.tsx b/src/pages/anti-dark-analysis-network/index.tsx new file mode 100644 index 0000000..8290737 --- /dev/null +++ b/src/pages/anti-dark-analysis-network/index.tsx @@ -0,0 +1,309 @@ +import { useState } from "react"; +import { useSelector } from "react-redux"; +import { WorldGeo } from "./components/world-geo"; + +import NetflixSvg from "@/assets/svg/anti-forensics-forwarding/Netflix.svg?react"; +import NetflixActiveSvg from "@/assets/svg/anti-forensics-forwarding/NetflixActive.svg?react"; +import SpotifySvg from "@/assets/svg/anti-forensics-forwarding/Spotify.svg?react"; +import SpotifyActiveSvg from "@/assets/svg/anti-forensics-forwarding/SpotifyActive.svg?react"; +import InstagramSvg from "@/assets/svg/anti-forensics-forwarding/Instagram.svg?react"; +import InstagramActiveSvg from "@/assets/svg/anti-forensics-forwarding/InstagramActive.svg?react"; +import TelegramSvg from "@/assets/svg/anti-forensics-forwarding/Telegram.svg?react"; +import TelegramActiveSvg from "@/assets/svg/anti-forensics-forwarding/TelegramActive.svg?react"; +import GoogleSvg from "@/assets/svg/anti-forensics-forwarding/Google.svg?react"; +import GoogleActiveSvg from "@/assets/svg/anti-forensics-forwarding/GoogleActive.svg?react"; +import GmailSvg from "@/assets/svg/anti-forensics-forwarding/Gmail.svg?react"; +import GmailActiveSvg from "@/assets/svg/anti-forensics-forwarding/GmailActive.svg?react"; +import AmazonSvg from "@/assets/svg/anti-forensics-forwarding/Amazon.svg?react"; +import AmazonActiveSvg from "@/assets/svg/anti-forensics-forwarding/AmazonActive.svg?react"; +import EbaySvg from "@/assets/svg/anti-forensics-forwarding/Ebay.svg?react"; +import EbayActiveSvg from "@/assets/svg/anti-forensics-forwarding/EbayActive.svg?react"; +import AppleNewsSvg from "@/assets/svg/anti-forensics-forwarding/AppleNews.svg?react"; +import AppleNewsActiveSvg from "@/assets/svg/anti-forensics-forwarding/AppleNewsActive.svg?react"; +import CNNSvg from "@/assets/svg/anti-forensics-forwarding/CNN.svg?react"; +import CNNActiveSvg from "@/assets/svg/anti-forensics-forwarding/CNNActive.svg?react"; +import BrowserSvg from "@/assets/svg/anti-forensics-forwarding/Browser.svg?react"; +import BrowserActiveSvg from "@/assets/svg/anti-forensics-forwarding/BrowserActive.svg?react"; +import YouTubeSvg from "@/assets/svg/anti-forensics-forwarding/YouTube.svg?react"; +import YouTubeActiveSvg from "@/assets/svg/anti-forensics-forwarding/YouTubeActive.svg?react"; +import FacebookSvg from "@/assets/svg/anti-forensics-forwarding/Facebook.svg?react"; +import FacebookActiveSvg from "@/assets/svg/anti-forensics-forwarding/FacebookActive.svg?react"; +import { RootState } from "@/store"; + + + +import "./index.scss"; +import { + getApplicationDiversion, + getDynamicRouteGeneration, + getNestedEncryption, + getPassAuthentication, + getTrafficObfuscation, +} from "@/api/flying-line"; + +export const DIALOGTYPE = { + ADDNode: { + title: "添加节点", + desc: "", + successText: "添加", + }, + AddNetwork: { + title: "构建网络", + desc: "", + successText: "构建", + }, +}; + +export const NODEDIALOGTYPE = { + ClearFailNode: { + title: "清除掉线节点", + desc: "", + successText: "清除", + }, + ClearWargingNode: { + title: "恶意节点", + desc: "", + successText: "清除", + }, +}; + +export const Apps = [ + { + name: "Netflix", + icon: NetflixSvg, + activeIcon: NetflixActiveSvg, + }, + { + name: "Spotify", + icon: SpotifySvg, + activeIcon: SpotifyActiveSvg, + }, + { + name: "Instagram", + icon: InstagramSvg, + activeIcon: InstagramActiveSvg, + }, + { + name: "Telegram", + icon: TelegramSvg, + activeIcon: TelegramActiveSvg, + }, + { + name: "Google", + icon: GoogleSvg, + activeIcon: GoogleActiveSvg, + }, + { + name: "Gmail", + icon: GmailSvg, + activeIcon: GmailActiveSvg, + }, + { + name: "Amazon", + icon: AmazonSvg, + activeIcon: AmazonActiveSvg, + }, + { + name: "Ebay", + icon: EbaySvg, + activeIcon: EbayActiveSvg, + }, + { + name: "AppleNews", + icon: AppleNewsSvg, + activeIcon: AppleNewsActiveSvg, + }, + { + name: "CNN", + icon: CNNSvg, + activeIcon: CNNActiveSvg, + }, + { + name: "Browser", + icon: BrowserSvg, + activeIcon: BrowserActiveSvg, + }, + { + name: "YouTube", + icon: YouTubeSvg, + activeIcon: YouTubeActiveSvg, + }, + { + name: "Facebook", + icon: FacebookSvg, + activeIcon: FacebookActiveSvg, + }, +]; + +export const CONST_TOOLTIP_TYPE = { + NESTED_ENCRYPTION: { + type: "NESTED_ENCRYPTION", + title: "嵌套加密", + }, + TRAFFIC_OBFUSCATION: { + type: "TRAFFIC_OBFUSCATION", + title: "流量混淆", + }, + DYNAMIC_ROUTE_GENERATOR: { + type: "DYNAMIC_ROUTE_GENERATOR", + title: "动态路由生成", + }, + // 应用分流 + APP_DIVERSION: { + type: "APP_DIVERSION", + title: "应用分流", + }, + // 通信认证 + PASS_AUTHENTICATION: { + type: "PASS_AUTHENTICATION", + title: "通信认证", + }, +}; + +const AntiDarkAnalysisNetwork = () => { + const { newHomeProxies } = useSelector( + (state: RootState) => state.web3Reducer + ); + + const [tooltipType, setTooltipType] = useState( + CONST_TOOLTIP_TYPE.PASS_AUTHENTICATION.type + ); + const [tooltipClosed, setTooltipClosed] = useState(false); + + const [selectedApp, setSelectedApp] = useState(null); + const [dataInfo, setDataInfo] = useState(null); + + const currentValue = useMemo(() => { + let value = dataInfo; + + switch (tooltipType) { + case CONST_TOOLTIP_TYPE.APP_DIVERSION.type: + value = selectedApp ? [selectedApp] : []; + break; + default: + break; + } + return value; + }, [tooltipType, selectedApp,dataInfo]); + + const handleClickApp = (item: any) => { + setSelectedApp(item); + }; + + const getDataInfo = async () => { + let value = []; + + switch (tooltipType) { + case CONST_TOOLTIP_TYPE.NESTED_ENCRYPTION.type: + const nestedEncryption = await getNestedEncryption(); + value = [nestedEncryption.data]; + break; + case CONST_TOOLTIP_TYPE.TRAFFIC_OBFUSCATION.type: + const trafficObfuscation = await getTrafficObfuscation(); + value = [trafficObfuscation.data]; + break; + case CONST_TOOLTIP_TYPE.DYNAMIC_ROUTE_GENERATOR.type: + const dynamicRouteGeneration = await getDynamicRouteGeneration(); + value = dynamicRouteGeneration.data; + break; + // case CONST_TOOLTIP_TYPE.APP_DIVERSION.type: + // const applicationDiversion = await getApplicationDiversion(); + + // value = selectedApp ? [selectedApp] : []; + // break; + case CONST_TOOLTIP_TYPE.PASS_AUTHENTICATION.type: + const passAuthentication = await getPassAuthentication(); + value = [passAuthentication.data]; + + break; + default: + break; + } + console.log(value,'valuevalue') + setDataInfo(value); + }; + + const [appData, setAppData] = useState([]); + const appDiversion = useMemo(() => { + return Apps.map((item) => { + const findApp = appData.find( + (appItem:any) => item.name === appItem.name + ); + return { + ...item, + ...findApp, + }; + }); + }, [appData]); + const initData = async () => { + const applicationDiversion = await getApplicationDiversion(); + + setAppData(applicationDiversion.data); + setTooltipType(CONST_TOOLTIP_TYPE.TRAFFIC_OBFUSCATION.type); + setTooltipClosed(true); + }; + + useEffect(() => { + getDataInfo(); + },[tooltipType]) + + useEffect(() => { + initData(); + () => { + setTooltipClosed(false); + }; + }, []); + + return ( +
+
+ {tooltipType === CONST_TOOLTIP_TYPE.APP_DIVERSION.type && + appDiversion.map((item) => { + return ( +
handleClickApp(item)} + > + {selectedApp?.name === item?.name ? ( + + ) : ( + + )} +
+ ); + })} +
+
+ +
+
+
{ + setTooltipType(CONST_TOOLTIP_TYPE.TRAFFIC_OBFUSCATION.type); + setTooltipClosed(true); + }} + > + 流量混淆 +
+
{ + setTooltipType(CONST_TOOLTIP_TYPE.APP_DIVERSION.type); + }} + > + 应用分流 +
+
+
+ ); +}; + +export default AntiDarkAnalysisNetwork; diff --git a/src/pages/anti-forensics-forwarding/index.tsx b/src/pages/anti-forensics-forwarding/index.tsx index e68dbaf..7db2436 100644 --- a/src/pages/anti-forensics-forwarding/index.tsx +++ b/src/pages/anti-forensics-forwarding/index.tsx @@ -314,7 +314,7 @@ const DecentralizedElasticNetwork = () => { > 动态路由生成 -
{ setTooltipType(CONST_TOOLTIP_TYPE.TRAFFIC_OBFUSCATION.type); @@ -330,7 +330,7 @@ const DecentralizedElasticNetwork = () => { }} > 应用分流 -
+ */} ); diff --git a/src/routes/index.tsx b/src/routes/index.tsx index 3838f52..aac9cb1 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -3,6 +3,7 @@ import HomePage from '@/pages/home' import NewHomePage from '@/pages/new-home' import DecentralizedElasticNetworkPage from '@/pages/decentralized-lastic-network' import AntiForensicsForwardingPage from '@/pages/anti-forensics-forwarding' +import AntiDarkAnalysisNetwork from '@/pages/anti-dark-analysis-network' import LazyLoader from '@/layout/LazyLoader' import App from '@/App' @@ -29,6 +30,10 @@ export const router = createBrowserRouter([ path: '/anti-forensics-forwarding', element: , }, + { + path: '/anti-dark-analysis-network', + element: , + }, // { // path: '/home', // element: ,