diff --git a/src/index.scss b/src/index.scss
index 5bcfe37..584366b 100644
--- a/src/index.scss
+++ b/src/index.scss
@@ -7,6 +7,20 @@ body {
}
+
+.scrollbar-visible {
+ scrollbar-width: auto;
+ /* For Firefox */
+ -ms-overflow-style: scrollbar;
+ /* For Internet Explorer and Edge */
+}
+
+.scrollbar-visible::-webkit-scrollbar {
+ display: block;
+ /* For Chrome, Safari, and Opera */
+ height: 10px;
+}
+
::selection {
// background-color: #18181b;
background-color: #1E3A8A;
@@ -17,7 +31,7 @@ body {
::-webkit-scrollbar {
width: 6px;
height: 6px;
- /* background-color: red; */
+// background-color: red;
}
/* ::-webkit-scrollbar-track {
@@ -103,4 +117,4 @@ body {
/* Internet Explorer/Edge (旧版) */
user-select: none;
/* Non-prefixed version, currently supported by Chrome, Edge, Opera and Firefox */
-}
+}
\ No newline at end of file
diff --git a/src/layout/index.tsx b/src/layout/index.tsx
index 6318eca..f1feac0 100644
--- a/src/layout/index.tsx
+++ b/src/layout/index.tsx
@@ -9,100 +9,102 @@ import TitleSvg from "@/assets/svg/layout/title.svg?react";
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 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";
export default function Layout() {
- const [_, setActive] = useState(0);
- const { coreVersion } = useSelector(
- (state: RootState) => state.serviceReducer
- );
+ const [_, setActive] = useState(0);
+ const { coreVersion } = useSelector(
+ (state: RootState) => state.serviceReducer
+ );
- const navList = [
- {
- id: "new-home",
- title: "首页",
- icon: ,
- },
- {
- id: "home",
- title: "去中心化的弹性网络",
- icon: ,
- },
- {
- id: "anti-forensics-forwarding",
- title: "面向溯源对抗的数据转发",
- icon: ,
- },
- {
- id: "anti-dark-analysis-network",
- title: "抗暗特征分析的隐匿网络应用",
- icon: ,
- },
- // {
- // id: 'proxies',
- // title: '节点池',
- // icon: ,
- // },
- ];
+ const navList = [
+ {
+ id: "new-home",
+ title: "首页",
+ icon: ,
+ },
+ {
+ id: "home",
+ title: "去中心化的弹性网络",
+ icon: ,
+ },
+ {
+ id: "anti-forensics-forwarding",
+ title: "面向溯源对抗的数据转发",
+ icon: ,
+ },
+ {
+ id: "anti-dark-analysis-network",
+ title: "抗暗特征分析的隐匿网络应用",
+ icon: ,
+ },
+ // {
+ // id: 'proxies',
+ // title: '节点池',
+ // icon: ,
+ // },
+ ];
- const handleClickMenu = (index: number) => {
- setActive(index);
- };
+ const handleClickMenu = (index: number) => {
+ setActive(index);
+ };
- return (
-
-
-
-
-
-
-
- {/*
匿名反溯源网络系统 */}
- {/*
Anonymous anti traceability network system */}
-
-
-
-
-
-
-
-
+ return (
+
+
+
+
+
+
+
+ {/*
匿名反溯源网络系统 */}
+ {/*
Anonymous anti traceability network system */}
+
+
+
+
+
+
+
+
- );
+
+
+ );
}
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
deleted file mode 100644
index 472bb7b..0000000
--- a/src/pages/anti-dark-analysis-network/components/world-geo copy 2.tsx
+++ /dev/null
@@ -1,949 +0,0 @@
-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
deleted file mode 100644
index 33e7b69..0000000
--- a/src/pages/anti-dark-analysis-network/components/world-geo copy.tsx
+++ /dev/null
@@ -1,876 +0,0 @@
-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
index 3eb0745..116d7c4 100644
--- a/src/pages/anti-dark-analysis-network/components/world-geo.tsx
+++ b/src/pages/anti-dark-analysis-network/components/world-geo.tsx
@@ -1,4 +1,4 @@
-import { useEffect, useMemo, useRef, memo } from "react";
+import { useEffect, useMemo, useRef, memo, useState } from "react";
import * as echarts from "echarts";
// import 'echarts-gl';
// import { useQueryClient } from "@tanstack/react-query";
@@ -6,9 +6,8 @@ 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;
@@ -17,6 +16,147 @@ interface LinesItemType {
}
type LinesDataType = [LinesItemType, LinesItemType];
type LinesType = [string, LinesDataType[]];
+
+
+// 创建左侧自定义提示框组件
+const CustomTooltipLeft = ({
+ logs = [],
+ onClose,
+ tooltipRef,
+ title,
+}: {
+ logs?: string[],
+ onClose: () => void,
+ tooltipRef: React.RefObject,
+ title: string,
+}) => {
+ const [visibleLogs, setVisibleLogs] = useState([]);
+ const [isComplete, setIsComplete] = useState(false);
+
+ // 过滤掉空日志
+ const filteredLogs = useMemo(() => {
+ return logs.filter(log => log && log.trim() !== '');
+ }, [logs]);
+
+ // 使用useEffect实现逐条显示日志的效果
+ useEffect(() => {
+ if (!filteredLogs || filteredLogs.length === 0) return;
+
+ // 重置状态
+ setVisibleLogs([]);
+ setIsComplete(false);
+
+ // 先显示第一条日志
+ setVisibleLogs([filteredLogs[0]]);
+
+ // 如果只有一条日志,直接设置完成
+ if (filteredLogs.length === 1) {
+ setIsComplete(true);
+ return;
+ }
+
+ // 从第二条日志开始,每500毫秒显示一条
+ let currentIndex = 1;
+
+ const timer = setInterval(() => {
+ if (currentIndex < filteredLogs.length) {
+ setVisibleLogs(prev => [...prev, filteredLogs[currentIndex]]);
+ currentIndex++;
+
+ // 如果已经是最后一条,设置完成状态
+ if (currentIndex >= filteredLogs.length) {
+ clearInterval(timer);
+ setIsComplete(true);
+ }
+ } else {
+ clearInterval(timer);
+ setIsComplete(true);
+ }
+ }, 500);
+
+ // 清理函数
+ return () => {
+ clearInterval(timer);
+ };
+ }, [filteredLogs]); // 当过滤后的日志变化时重新开始动画
+
+ // 自动滚动到最新的日志
+ const logsContainerRef = useRef(null);
+
+ useEffect(() => {
+ if (logsContainerRef.current && visibleLogs.length > 0) {
+ logsContainerRef.current.scrollTop = logsContainerRef.current.scrollHeight;
+ }
+ }, [visibleLogs]);
+
+ return (
+
+ );
+};
// 创建单个国家的涟漪效果
const createCountryRipple = (countryCode: string, color?: string) => {
const coords = geoCoordMap[countryCode];
@@ -28,6 +168,7 @@ const createCountryRipple = (countryCode: string, color?: string) => {
color: color || "#0ea5e9", // 添加颜色属性,如果没有则使用默认颜色
};
};
+
export const WorldGeo = memo(
({
currentValue,
@@ -35,11 +176,13 @@ export const WorldGeo = memo(
tooltipType,
tooltipClosed,
setTooltipClosed,
+ trafficObfuscationLogs,
}: {
currentValue: any;
newHomeProxies: any;
tooltipType: string;
tooltipClosed: boolean;
+ trafficObfuscationLogs:any;
setTooltipClosed: (value: boolean) => void;
}) => {
// const queryClient = useQueryClient()
@@ -59,6 +202,13 @@ export const WorldGeo = memo(
>([]);
const labelContainerRef = useRef(null);
const labelsRef = useRef([]);
+
+ // 添加状态来控制是否显示tooltip
+ const [showTooltip1, setShowTooltip1] = useState(false);
+ const [showTooltip2, setShowTooltip2] = useState(false);
+
+
+
const mainToData = useMemo(() => {
// 使用新的数据结构
const proxiesList = currentValue ?? [];
@@ -99,69 +249,7 @@ export const WorldGeo = memo(
});
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;
@@ -184,57 +272,8 @@ export const WorldGeo = memo(
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();
- };
- // 定位自定义提示框 - 优化版本
+
+ // 定位自定义提示框2 - 优化版本
const positionCustomTooltip2 = () => {
if (!customTooltip2Ref.current || !proxyGeoRef.current) return;
// 找到US点
@@ -250,16 +289,25 @@ export const WorldGeo = memo(
) {
// 设置提示框位置
customTooltip2Ref.current.style.left = `${
- screenCoord[0] - 626 + 20
+ screenCoord[0] - 626 + 53
}px`;
customTooltip2Ref.current.style.top = `${
- screenCoord[1] + 40 - 218
+ screenCoord[1] + 40 - 222
}px`;
}
} catch (error) {
console.error("Error positioning tooltip:", error);
}
};
+
+
+
+ // 处理关闭tooltip2
+ const handleCloseTooltip2 = () => {
+ setShowTooltip2(false);
+ setTooltipClosed(false);
+ };
+
const getLineItem = (
preCode: string,
nextCode: string
@@ -277,28 +325,25 @@ export const WorldGeo = memo(
},
];
};
+
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(
@@ -307,7 +352,6 @@ export const WorldGeo = memo(
);
if (startPoint) ripplePoints.push(startPoint);
if (endPoint) ripplePoints.push(endPoint);
-
// 检查是否应该绘制连线
if (currentItem.isLine !== false) {
const lineItem = getLineItem(startCode, endCode);
@@ -316,14 +360,12 @@ export const WorldGeo = memo(
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(
@@ -332,7 +374,6 @@ export const WorldGeo = memo(
);
if (currentPoint) ripplePoints.push(currentPoint);
if (nextPoint) ripplePoints.push(nextPoint);
-
// 检查是否应该绘制连线
if (currentItem.isLine !== false) {
const lineItem = getLineItem(countryCode, nextCountryCode);
@@ -343,7 +384,6 @@ export const WorldGeo = memo(
}
}
}
-
// 虚线数据处理(保持原有逻辑)
const otherLineList: any = [];
return {
@@ -352,6 +392,7 @@ export const WorldGeo = memo(
ripplePoints,
};
};
+
// 获取连线经纬度数据
const convertData = (data: LinesDataType[]) => {
const res = [];
@@ -394,6 +435,7 @@ export const WorldGeo = memo(
// lineMidpointsRef.current = midpoints;
return res;
};
+
// 创建双层点效果 - 大点
const createDualLayerPoint = (
lastExit: LinesItemType,
@@ -480,6 +522,7 @@ export const WorldGeo = memo(
} as echarts.SeriesOption,
];
};
+
// 添加新方法:根据经纬度数组创建蓝色涟漪小点(不包含白色内层点)
const createRipplePointsFromCoordinates = (
coordinates: [number, number][],
@@ -511,6 +554,7 @@ export const WorldGeo = memo(
})),
} as echarts.SeriesOption);
};
+
// 创建路径点的双层效果
const createPathPoints = (
dataItems: LinesDataType[],
@@ -593,6 +637,7 @@ export const WorldGeo = memo(
} as echarts.SeriesOption,
];
};
+
// 创建带自定义提示框的涟漪点
const createRipplePointsWithTooltip = (ripplePoints: any) => {
return {
@@ -652,6 +697,7 @@ export const WorldGeo = memo(
})),
} as echarts.SeriesOption;
};
+
// 连线 series
const getLianData = (series: echarts.SeriesOption[]) => {
const { solidData, otherLineList, ripplePoints } = getLine();
@@ -688,7 +734,6 @@ export const WorldGeo = memo(
const lastExit = item[1]?.[item[1].length - 1]?.[1] ?? null;
// 获取当前路径的颜色
const pathColor = item[1]?.[0]?.[0]?.color || "#0ea5e9"; // 从第一个点获取颜色,如果没有则使用默认颜色
-
// 添加飞行线
series.push({
name: item[0],
@@ -716,11 +761,9 @@ export const WorldGeo = memo(
},
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);
@@ -732,7 +775,6 @@ export const WorldGeo = memo(
const lastExit = item[1]?.[item[1].length - 1]?.[1] ?? null;
// 获取当前路径的颜色
const pathColor = item[1]?.[0]?.[0]?.color || "#F0FFA2"; // 从第一个点获取颜色,如果没有则使用默认颜色
-
// 添加虚线
series.push({
name: item[0],
@@ -906,6 +948,7 @@ export const WorldGeo = memo(
});
return series;
};
+
const getOption = () => {
const series: echarts.SeriesOption[] = [];
getLianData(series);
@@ -917,14 +960,12 @@ export const WorldGeo = memo(
currentValue[0]?.authenticationPoint
) {
console.log(currentValue, "values");
-
createSpecialPoints(series); // 添加特殊点和飞线
createRipplePointsFromCoordinates(
currentValue[0]?.authenticationPoint || [],
series
);
}
-
const option = {
backgroundColor: "transparent",
// 全局提示框配置
@@ -1000,6 +1041,7 @@ export const WorldGeo = memo(
};
return option;
};
+
// 创建DOM标签
const createDOMLabels = () => {
// 清除现有标签
@@ -1065,6 +1107,7 @@ export const WorldGeo = memo(
// 更新标签位置
updateLabelPositions();
};
+
// 更新标签位置
const updateLabelPositions = () => {
if (!proxyGeoRef.current || !labelContainerRef.current) return;
@@ -1081,10 +1124,20 @@ export const WorldGeo = memo(
}
});
};
+
const handleResize = () => {
proxyGeoRef.current?.resize();
updateLabelPositions();
+
+ // 重新定位tooltip
+ if (showTooltip1) {
+ positionCustomTooltip();
+ }
+ if (showTooltip2) {
+ positionCustomTooltip2();
+ }
};
+
useEffect(() => {
preMainToData.current?.some(
(item, index) => item.country_code !== mainToData[index]?.country_code
@@ -1095,6 +1148,7 @@ export const WorldGeo = memo(
// 创建DOM标签
setTimeout(createDOMLabels, 100);
}, [newHomeProxies, mainToData]);
+
useEffect(() => {
const chartDom = document.getElementById("screenGeo");
proxyGeoRef.current = echarts.init(chartDom);
@@ -1121,34 +1175,68 @@ export const WorldGeo = memo(
proxyGeoRef.current = null;
};
}, []);
+
+ // 处理tooltip的显示和隐藏
useEffect(() => {
if (tooltipType !== "PASS_AUTHENTICATION") {
lineMidpointsRef.current = [];
}
+
if (tooltipClosed) {
if (tooltipType === "NESTED_ENCRYPTION") {
- createCustomTooltip();
+ setShowTooltip1(true);
+ // 在下一个渲染周期后定位tooltip
+ setTimeout(() => {
+ positionCustomTooltip();
+ }, 0);
}
if (tooltipType === "TRAFFIC_OBFUSCATION") {
- createCustomTooltip2();
+ setShowTooltip2(true);
+ // 在下一个渲染周期后定位tooltip
+ setTimeout(() => {
+ positionCustomTooltip2();
+ }, 0);
}
} else {
- customTooltipRef.current?.remove();
- customTooltip2Ref.current?.remove();
- customTooltipRef.current = null;
- customTooltip2Ref.current = null;
+ setShowTooltip1(false);
+ setShowTooltip2(false);
}
- return () => {
- customTooltipRef.current?.remove();
- customTooltip2Ref.current?.remove();
- customTooltipRef.current = null;
- customTooltip2Ref.current = null;
- };
}, [tooltipClosed, tooltipType, currentValue]);
+
+ // 在地图初始化后定位tooltip
+ useEffect(() => {
+ if (showTooltip1) {
+ positionCustomTooltip();
+ }
+ if (showTooltip2) {
+ positionCustomTooltip2();
+ }
+ }, [showTooltip1, showTooltip2]);
return (
+
+ {/* 流量混淆提示框 */}
+ {showTooltip2 && (
+
+ )}
);
}
);
+
+// 添加CSS样式
+// 可以放在你的全局CSS文件中
+// @keyframes fadeIn {
+// from { opacity: 0; transform: translateY(5px); }
+// to { opacity: 1; transform: translateY(0); }
+// }
+//
+// .animate-fadeIn {
+// animation: fadeIn 0.3s ease-out forwards;
+// }
\ No newline at end of file
diff --git a/src/pages/anti-dark-analysis-network/index.scss b/src/pages/anti-dark-analysis-network/index.scss
index ad5969c..1f1fe1a 100644
--- a/src/pages/anti-dark-analysis-network/index.scss
+++ b/src/pages/anti-dark-analysis-network/index.scss
@@ -81,11 +81,10 @@
font-weight: 500;
// line-height: 24px;
}
-
-.tip-box {
+.tip-box-left{
position: relative;
- width: 626px;
- height: 281px;
+ width: 600px;
+ height: 400px;
padding: 20.85px 20.353px;
background: rgba(0, 11.82, 33.10, 0.10);
border-radius: 8px;
@@ -93,7 +92,30 @@
outline-offset: -0.46px;
backdrop-filter: blur(5.50px);
- .close-icon , .close-icon2 {
+}
+.line-img-left {
+ width: 216.86px;
+ // margin-top: 30px;
+ right: -216.86px;
+ top: 60px;
+ position: absolute;
+}
+.tip-box-hx {
+ position: relative;
+ width: 600px;
+ height: 400px;
+ margin-left: 312.221px;
+ // min-height: 200px;
+ // max-height: 600px;
+ 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;
@@ -116,7 +138,7 @@
margin-left: 16px;
}
- .traffic-obfuscation-img{
+ .traffic-obfuscation-img {
width: 597px;
height: 241px;
margin-left: 16px;
@@ -127,85 +149,13 @@
position: relative;
display: flex;
- .line-img {
+ .line-img-hx {
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;
+ // margin-top: 80px;
+ top: 80px;
+ left: 0px;
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
index 8290737..3fd99c8 100644
--- a/src/pages/anti-dark-analysis-network/index.tsx
+++ b/src/pages/anti-dark-analysis-network/index.tsx
@@ -30,8 +30,6 @@ import FacebookSvg from "@/assets/svg/anti-forensics-forwarding/Facebook.svg?rea
import FacebookActiveSvg from "@/assets/svg/anti-forensics-forwarding/FacebookActive.svg?react";
import { RootState } from "@/store";
-
-
import "./index.scss";
import {
getApplicationDiversion,
@@ -172,10 +170,17 @@ const AntiDarkAnalysisNetwork = () => {
const [selectedApp, setSelectedApp] = useState(null);
const [dataInfo, setDataInfo] = useState(null);
-
+ const [trafficObfuscationLogs, setTrafficObfuscationLogs] = useState([
+ "初始化嵌套加密...",
+ "生成密钥对222...",
+ "应用第一层加密...",
+ "应用第二层加密...",
+ "应用第三层加密...",
+ "加密完成,准备传输...",
+ ]);
const currentValue = useMemo(() => {
let value = dataInfo;
-
+
switch (tooltipType) {
case CONST_TOOLTIP_TYPE.APP_DIVERSION.type:
value = selectedApp ? [selectedApp] : [];
@@ -184,7 +189,7 @@ const AntiDarkAnalysisNetwork = () => {
break;
}
return value;
- }, [tooltipType, selectedApp,dataInfo]);
+ }, [tooltipType, selectedApp, dataInfo]);
const handleClickApp = (item: any) => {
setSelectedApp(item);
@@ -201,6 +206,7 @@ const AntiDarkAnalysisNetwork = () => {
case CONST_TOOLTIP_TYPE.TRAFFIC_OBFUSCATION.type:
const trafficObfuscation = await getTrafficObfuscation();
value = [trafficObfuscation.data];
+ setTrafficObfuscationLogs(trafficObfuscation.logs);
break;
case CONST_TOOLTIP_TYPE.DYNAMIC_ROUTE_GENERATOR.type:
const dynamicRouteGeneration = await getDynamicRouteGeneration();
@@ -219,7 +225,6 @@ const AntiDarkAnalysisNetwork = () => {
default:
break;
}
- console.log(value,'valuevalue')
setDataInfo(value);
};
@@ -227,7 +232,7 @@ const AntiDarkAnalysisNetwork = () => {
const appDiversion = useMemo(() => {
return Apps.map((item) => {
const findApp = appData.find(
- (appItem:any) => item.name === appItem.name
+ (appItem: any) => item.name === appItem.name
);
return {
...item,
@@ -245,7 +250,7 @@ const AntiDarkAnalysisNetwork = () => {
useEffect(() => {
getDataInfo();
- },[tooltipType])
+ }, [tooltipType]);
useEffect(() => {
initData();
@@ -276,6 +281,7 @@ const AntiDarkAnalysisNetwork = () => {
void;
+ tooltipRef: React.RefObject;
+}) => {
+ const [visibleLogs, setVisibleLogs] = useState([]);
+ const [isComplete, setIsComplete] = useState(false);
+
+ // 使用useEffect实现逐条显示日志的效果
+ useEffect(() => {
+ console.log("logs-------", logs.length);
+ if (!logs || logs.length === 0) return;
+
+ // 重置状态
+ setVisibleLogs([]);
+ setIsComplete(false);
+
+ let currentIndex = 0;
+
+ // 创建一个定时器,每500毫秒显示一条新日志
+ const timer = setInterval(() => {
+ if (currentIndex < logs.length) {
+ setVisibleLogs((prev) => [...prev, logs[currentIndex]]);
+ currentIndex++;
+ } else {
+ clearInterval(timer);
+ setIsComplete(true);
+ }
+ }, 500);
+
+ // 清理函数
+ return () => {
+ clearInterval(timer);
+ };
+ }, [logs]); // 当logs变化时重新开始动画
+
+ // 自动滚动到最新的日志
+ const logsContainerRef = useRef(null);
+
+ useEffect(() => {
+ if (logsContainerRef.current && visibleLogs.length > 0) {
+ logsContainerRef.current.scrollTop =
+ logsContainerRef.current.scrollHeight;
+ }
+ }, [visibleLogs]);
+
+ return (
+
+ );
+};
+
+// 添加一个淡入动画的CSS(可以放在你的全局CSS文件中)
+// @keyframes fadeIn {
+// from { opacity: 0; transform: translateY(5px); }
+// to { opacity: 1; transform: translateY(0); }
+// }
+//
+// .animate-fadeIn {
+// animation: fadeIn 0.3s ease-out forwards;
+// }
+
// 创建单个国家的涟漪效果
const createCountryRipple = (countryCode: string, color?: string) => {
const coords = geoCoordMap[countryCode];
@@ -27,13 +157,17 @@ const createCountryRipple = (countryCode: string, color?: string) => {
color: color || "#0ea5e9", // 添加颜色属性,如果没有则使用默认颜色
};
};
+
export const WorldGeo = memo(
({
nestedEncryption,
passAuthentication,
dynamicRouteGeneration,
+ tooltipClosed,
setTooltipClosed,
+ logs,
}: {
+ logs:any[];
nestedEncryption: any;
passAuthentication: any;
dynamicRouteGeneration: any;
@@ -56,33 +190,38 @@ export const WorldGeo = memo(
>([]);
const labelContainerRef = useRef(null);
const labelsRef = useRef([]);
-
+
// 添加状态来跟踪当前显示的连线索引
- const [nestedEncryptionLineIndex, setNestedEncryptionLineIndex] = useState(-1);
+ const [nestedEncryptionLineIndex, setNestedEncryptionLineIndex] =
+ useState(-1);
const [dynamicRouteLineIndex, setDynamicRouteLineIndex] = useState(-1);
-
+
// 添加状态来存储所有连线数据
- const [nestedEncryptionLines, setNestedEncryptionLines] = useState<{from: string, to: string, color?: string}[]>([]);
- const [dynamicRouteLines, setDynamicRouteLines] = useState<{from: string, to: string, color?: string}[]>([]);
-
+ const [nestedEncryptionLines, setNestedEncryptionLines] = useState<
+ { from: string; to: string; color?: string }[]
+ >([]);
+ const [dynamicRouteLines, setDynamicRouteLines] = useState<
+ { from: string; to: string; color?: string }[]
+ >([]);
+
// 添加状态来存储所有点
const [allPoints, setAllPoints] = useState([]);
-
+
// 使用ref来跟踪动画状态,避免重新渲染
const animationTimerRef = useRef(null);
const dynamicAnimationTimerRef = useRef(null);
-
+
// 添加状态来跟踪数据是否已经变化
const nestedEncryptionKeyRef = useRef("");
const dynamicRouteKeyRef = useRef("");
-
+
// 初始化时提取所有点的函数
const extractAllPoints = () => {
const points: any[] = [];
-
+
// console.log("Extracting points from nestedEncryption:", nestedEncryption);
// console.log("Extracting points from dynamicRouteGeneration:", dynamicRouteGeneration);
-
+
// 从嵌套加密数据中提取点
if (nestedEncryption && Array.isArray(nestedEncryption)) {
nestedEncryption.forEach((item: any) => {
@@ -91,15 +230,18 @@ export const WorldGeo = memo(
// 添加起点到点集合
const fromCode = dataItem.country_code.toUpperCase();
const fromPoint = createCountryRipple(fromCode, item.color);
- if (fromPoint && !points.some(p => p.country_code === fromCode)) {
+ if (
+ fromPoint &&
+ !points.some((p) => p.country_code === fromCode)
+ ) {
points.push(fromPoint);
}
-
+
// 如果有终点,也添加到点集合
if (dataItem.ingress_country_code) {
const toCode = dataItem.ingress_country_code.toUpperCase();
const toPoint = createCountryRipple(toCode, item.color);
- if (toPoint && !points.some(p => p.country_code === toCode)) {
+ if (toPoint && !points.some((p) => p.country_code === toCode)) {
points.push(toPoint);
}
}
@@ -107,7 +249,7 @@ export const WorldGeo = memo(
}
});
}
-
+
// 从动态路由数据中提取点
if (dynamicRouteGeneration && Array.isArray(dynamicRouteGeneration)) {
dynamicRouteGeneration.forEach((item: any) => {
@@ -116,15 +258,18 @@ export const WorldGeo = memo(
// 添加起点到点集合
const fromCode = dataItem.country_code.toUpperCase();
const fromPoint = createCountryRipple(fromCode, item.color);
- if (fromPoint && !points.some(p => p.country_code === fromCode)) {
+ if (
+ fromPoint &&
+ !points.some((p) => p.country_code === fromCode)
+ ) {
points.push(fromPoint);
}
-
+
// 如果有终点,也添加到点集合
if (dataItem.ingress_country_code) {
const toCode = dataItem.ingress_country_code.toUpperCase();
const toPoint = createCountryRipple(toCode, item.color);
- if (toPoint && !points.some(p => p.country_code === toCode)) {
+ if (toPoint && !points.some((p) => p.country_code === toCode)) {
points.push(toPoint);
}
}
@@ -132,11 +277,11 @@ export const WorldGeo = memo(
}
});
}
-
+
console.log("Extracted points:", points);
return points;
};
-
+
// 修改初始化逻辑,确保在数据变化时立即提取点
useEffect(() => {
// 提取所有点
@@ -145,28 +290,33 @@ export const WorldGeo = memo(
setAllPoints(points);
}
}, [nestedEncryption, dynamicRouteGeneration]); // 监听数据变化
-
+
// 启动嵌套加密连线动画的函数
- const startNestedEncryptionAnimation = (connections: {from: string, to: string, color?: string}[]) => {
+ const startNestedEncryptionAnimation = (
+ connections: { from: string; to: string; color?: string }[]
+ ) => {
if (connections.length === 0) return;
-
+
let index = 0;
-
+
// 递归函数,用于按顺序显示连线
const animateNextLine = () => {
setNestedEncryptionLineIndex(index);
-
+
index++;
-
+
if (index < connections.length) {
- animationTimerRef.current = setTimeout(animateNextLine, LINE_ANIMATION_INTERVAL);
+ animationTimerRef.current = setTimeout(
+ animateNextLine,
+ LINE_ANIMATION_INTERVAL
+ );
}
};
-
+
// 开始动画
animateNextLine();
};
-
+
// 处理嵌套加密数据变化
useEffect(() => {
// 清除任何现有的动画定时器
@@ -174,45 +324,52 @@ export const WorldGeo = memo(
clearTimeout(animationTimerRef.current);
animationTimerRef.current = null;
}
-
+
const allExtractedPoints: any[] = [];
-
+
// 处理嵌套加密数据
if (nestedEncryption && Array.isArray(nestedEncryption)) {
const points: any[] = [];
- const connections: {from: string, to: string, color?: string}[] = [];
+ const connections: { from: string; to: string; color?: string }[] = [];
let shouldStartAnimation = false;
-
+
nestedEncryption.forEach((item: any) => {
if (item.data && Array.isArray(item.data)) {
item.data.forEach((dataItem: any) => {
// 添加起点到点集合
const fromCode = dataItem.country_code.toUpperCase();
const fromPoint = createCountryRipple(fromCode, item.color);
- if (fromPoint && !points.some(p => p.country_code === fromCode)) {
+ if (
+ fromPoint &&
+ !points.some((p) => p.country_code === fromCode)
+ ) {
points.push(fromPoint);
- if (!allExtractedPoints.some(p => p.country_code === fromCode)) {
+ if (
+ !allExtractedPoints.some((p) => p.country_code === fromCode)
+ ) {
allExtractedPoints.push(fromPoint);
}
}
-
+
// 如果有终点,也添加到点集合
if (dataItem.ingress_country_code) {
const toCode = dataItem.ingress_country_code.toUpperCase();
const toPoint = createCountryRipple(toCode, item.color);
- if (toPoint && !points.some(p => p.country_code === toCode)) {
+ if (toPoint && !points.some((p) => p.country_code === toCode)) {
points.push(toPoint);
- if (!allExtractedPoints.some(p => p.country_code === toCode)) {
+ if (
+ !allExtractedPoints.some((p) => p.country_code === toCode)
+ ) {
allExtractedPoints.push(toPoint);
}
}
-
+
// 检查是否需要开始连线动画
if (item.isLine === true) {
connections.push({
from: fromCode,
to: toCode,
- color: item.color
+ color: item.color,
});
shouldStartAnimation = true;
}
@@ -220,19 +377,22 @@ export const WorldGeo = memo(
});
}
});
-
+
// 生成当前数据的唯一键
const currentKey = JSON.stringify(nestedEncryption);
-
+
// 检查数据是否变化
- if (currentKey !== nestedEncryptionKeyRef.current || shouldStartAnimation) {
+ if (
+ currentKey !== nestedEncryptionKeyRef.current ||
+ shouldStartAnimation
+ ) {
nestedEncryptionKeyRef.current = currentKey;
setNestedEncryptionLines(connections);
-
+
// 如果有连线数据且需要开始动画,重置索引并启动动画
if (connections.length > 0 && shouldStartAnimation) {
setNestedEncryptionLineIndex(-1); // 重置索引
-
+
// 启动连线动画
setTimeout(() => {
startNestedEncryptionAnimation(connections);
@@ -243,17 +403,19 @@ export const WorldGeo = memo(
}
}
}
-
+
// 更新所有点
if (allExtractedPoints.length > 0) {
- setAllPoints(prevPoints => {
+ setAllPoints((prevPoints) => {
const newPoints = [...prevPoints];
- allExtractedPoints.forEach(point => {
- if (!newPoints.some(p => p.country_code === point.country_code)) {
+ allExtractedPoints.forEach((point) => {
+ if (!newPoints.some((p) => p.country_code === point.country_code)) {
newPoints.push(point);
} else {
// 更新已存在点的颜色
- const existingIndex = newPoints.findIndex(p => p.country_code === point.country_code);
+ const existingIndex = newPoints.findIndex(
+ (p) => p.country_code === point.country_code
+ );
if (existingIndex !== -1 && point.color) {
newPoints[existingIndex].color = point.color;
}
@@ -263,7 +425,7 @@ export const WorldGeo = memo(
});
}
}, [nestedEncryption]);
-
+
// 处理动态路由数据变化
useEffect(() => {
// 清除任何现有的动画定时器
@@ -271,45 +433,52 @@ export const WorldGeo = memo(
clearTimeout(dynamicAnimationTimerRef.current);
dynamicAnimationTimerRef.current = null;
}
-
+
const allExtractedPoints: any[] = [];
-
+
// 处理动态路由数据
if (dynamicRouteGeneration && Array.isArray(dynamicRouteGeneration)) {
const points: any[] = [];
- const connections: {from: string, to: string, color?: string}[] = [];
+ const connections: { from: string; to: string; color?: string }[] = [];
let shouldStartAnimation = false;
-
+
dynamicRouteGeneration.forEach((item: any) => {
if (item.data && Array.isArray(item.data)) {
item.data.forEach((dataItem: any) => {
// 添加起点到点集合
const fromCode = dataItem.country_code.toUpperCase();
const fromPoint = createCountryRipple(fromCode, item.color);
- if (fromPoint && !points.some(p => p.country_code === fromCode)) {
+ if (
+ fromPoint &&
+ !points.some((p) => p.country_code === fromCode)
+ ) {
points.push(fromPoint);
- if (!allExtractedPoints.some(p => p.country_code === fromCode)) {
+ if (
+ !allExtractedPoints.some((p) => p.country_code === fromCode)
+ ) {
allExtractedPoints.push(fromPoint);
}
}
-
+
// 如果有终点,也添加到点集合
if (dataItem.ingress_country_code) {
const toCode = dataItem.ingress_country_code.toUpperCase();
const toPoint = createCountryRipple(toCode, item.color);
- if (toPoint && !points.some(p => p.country_code === toCode)) {
+ if (toPoint && !points.some((p) => p.country_code === toCode)) {
points.push(toPoint);
- if (!allExtractedPoints.some(p => p.country_code === toCode)) {
+ if (
+ !allExtractedPoints.some((p) => p.country_code === toCode)
+ ) {
allExtractedPoints.push(toPoint);
}
}
-
+
// 检查是否需要开始连线动画
if (item.isLine === true) {
connections.push({
from: fromCode,
to: toCode,
- color: item.color
+ color: item.color,
});
shouldStartAnimation = true;
}
@@ -317,19 +486,19 @@ export const WorldGeo = memo(
});
}
});
-
+
// 生成当前数据的唯一键
const currentKey = JSON.stringify(dynamicRouteGeneration);
-
+
// 检查数据是否变化
if (currentKey !== dynamicRouteKeyRef.current || shouldStartAnimation) {
dynamicRouteKeyRef.current = currentKey;
setDynamicRouteLines(connections);
-
+
// 如果有连线数据且需要开始动画,重置索引并启动动画
if (connections.length > 0 && shouldStartAnimation) {
setDynamicRouteLineIndex(-1); // 重置索引
-
+
// 启动连线动画
setTimeout(() => {
startDynamicRouteAnimation(connections);
@@ -340,17 +509,19 @@ export const WorldGeo = memo(
}
}
}
-
+
// 更新所有点
if (allExtractedPoints.length > 0) {
- setAllPoints(prevPoints => {
+ setAllPoints((prevPoints) => {
const newPoints = [...prevPoints];
- allExtractedPoints.forEach(point => {
- if (!newPoints.some(p => p.country_code === point.country_code)) {
+ allExtractedPoints.forEach((point) => {
+ if (!newPoints.some((p) => p.country_code === point.country_code)) {
newPoints.push(point);
} else {
// 更新已存在点的颜色
- const existingIndex = newPoints.findIndex(p => p.country_code === point.country_code);
+ const existingIndex = newPoints.findIndex(
+ (p) => p.country_code === point.country_code
+ );
if (existingIndex !== -1 && point.color) {
newPoints[existingIndex].color = point.color;
}
@@ -360,28 +531,33 @@ export const WorldGeo = memo(
});
}
}, [dynamicRouteGeneration]);
-
+
// 启动动态路由连线动画的函数
- const startDynamicRouteAnimation = (connections: {from: string, to: string, color?: string}[]) => {
+ const startDynamicRouteAnimation = (
+ connections: { from: string; to: string; color?: string }[]
+ ) => {
if (connections.length === 0) return;
-
+
let index = 0;
-
+
// 递归函数,用于按顺序显示连线
const animateNextLine = () => {
setDynamicRouteLineIndex(index);
-
+
index++;
-
+
if (index < connections.length) {
- dynamicAnimationTimerRef.current = setTimeout(animateNextLine, LINE_ANIMATION_INTERVAL);
+ dynamicAnimationTimerRef.current = setTimeout(
+ animateNextLine,
+ LINE_ANIMATION_INTERVAL
+ );
}
};
-
+
// 开始动画
animateNextLine();
};
-
+
// 组件卸载时清除定时器
useEffect(() => {
return () => {
@@ -395,7 +571,7 @@ export const WorldGeo = memo(
}
};
}, []);
-
+
const getLineItem = (
preCode: string,
nextCode: string,
@@ -416,95 +592,57 @@ export const WorldGeo = memo(
},
];
};
-
+
const getLine = () => {
// 实现数据处理
const solidData: LinesType[] = []; // 不再使用单一数组,而是分开存储
-
+
// 处理嵌套加密连线 - 放入单独的数组
if (nestedEncryptionLineIndex >= 0 && nestedEncryptionLines.length > 0) {
const nestedLines: LinesDataType[] = [];
- for (let i = 0; i <= nestedEncryptionLineIndex && i < nestedEncryptionLines.length; i++) {
+ for (
+ let i = 0;
+ i <= nestedEncryptionLineIndex && i < nestedEncryptionLines.length;
+ i++
+ ) {
const connection = nestedEncryptionLines[i];
- nestedLines.push(getLineItem(connection.from, connection.to, connection.color));
+ nestedLines.push(
+ getLineItem(connection.from, connection.to, connection.color)
+ );
}
if (nestedLines.length > 0) {
solidData.push(["nested", nestedLines]);
}
}
-
+
// 处理动态路由连线 - 放入单独的数组
if (dynamicRouteLineIndex >= 0 && dynamicRouteLines.length > 0) {
const dynamicLines: LinesDataType[] = [];
- for (let i = 0; i <= dynamicRouteLineIndex && i < dynamicRouteLines.length; i++) {
+ for (
+ let i = 0;
+ i <= dynamicRouteLineIndex && i < dynamicRouteLines.length;
+ i++
+ ) {
const connection = dynamicRouteLines[i];
- dynamicLines.push(getLineItem(connection.from, connection.to, connection.color));
+ dynamicLines.push(
+ getLineItem(connection.from, connection.to, connection.color)
+ );
}
if (dynamicLines.length > 0) {
solidData.push(["dynamic", dynamicLines]);
}
}
-
+
// 虚线数据处理(保持原有逻辑)
const otherLineList: any = [];
-
+
return {
solidData,
otherLineList,
ripplePoints: allPoints, // 使用 allPoints 确保点始终显示
};
};
-
- // 创建自定义提示框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";
- tooltip.innerHTML = `
-
-

-
-
-
-
嵌套加密
-

-
-
-

-
-
-
- `;
- // 添加到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;
@@ -527,7 +665,13 @@ export const WorldGeo = memo(
console.error("Error positioning tooltip:", error);
}
};
-
+
+ // 处理关闭tooltip
+ const handleCloseTooltip = () => {
+ setTooltipClosed(false);
+ setTooltipClosed(false);
+ };
+
// 获取连线经纬度数据
const convertData = (data: LinesDataType[]) => {
const res = [];
@@ -570,7 +714,7 @@ export const WorldGeo = memo(
// lineMidpointsRef.current = midpoints;
return res;
};
-
+
// 创建双层点效果 - 大点
const createDualLayerPoint = (
lastExit: LinesItemType,
@@ -657,7 +801,7 @@ export const WorldGeo = memo(
} as echarts.SeriesOption,
];
};
-
+
// 添加新方法:根据经纬度数组创建蓝色涟漪小点(不包含白色内层点)
const createRipplePointsFromCoordinates = (
coordinates: [number, number][],
@@ -689,7 +833,7 @@ export const WorldGeo = memo(
})),
} as echarts.SeriesOption);
};
-
+
// 创建路径点的双层效果
const createPathPoints = (
dataItems: LinesDataType[],
@@ -772,7 +916,7 @@ export const WorldGeo = memo(
} as echarts.SeriesOption,
];
};
-
+
// 创建带自定义提示框的涟漪点
const createRipplePointsWithTooltip = (ripplePoints: any) => {
return {
@@ -832,11 +976,11 @@ export const WorldGeo = memo(
})),
} as echarts.SeriesOption;
};
-
+
// 连线 series
const getLianData = (series: echarts.SeriesOption[]) => {
const { solidData, otherLineList, ripplePoints } = getLine();
-
+
// 如果有需要显示涟漪效果的点,添加它们
if (ripplePoints.length > 0) {
// 添加带自定义提示框的外层蓝色点
@@ -862,17 +1006,17 @@ export const WorldGeo = memo(
})),
} as echarts.SeriesOption);
}
-
+
// 处理每个连线组
solidData.forEach((item) => {
// 如果没有连线数据,则跳过
if (item[1].length === 0) {
return;
}
-
+
// 为每条连线创建飞行线
const pathColor = item[0] === "nested" ? "#0ea5e9" : "#F0FFA2"; // 根据类型设置默认颜色
-
+
// 添加飞行线
series.push({
name: item[0],
@@ -899,21 +1043,25 @@ export const WorldGeo = memo(
},
data: convertData(item[1]) as echarts.LinesSeriesOption["data"],
});
-
+
// 添加路径点的双层效果
const pathPoints = createPathPoints(item[1], true, pathColor);
series.push(...pathPoints);
-
+
// 添加出口节点的双层效果
- item[1].forEach(lineData => {
+ item[1].forEach((lineData) => {
const lastExit = lineData[1];
if (lastExit) {
- const exitNodes = createDualLayerPoint(lastExit, true, lastExit.color || pathColor);
+ const exitNodes = createDualLayerPoint(
+ lastExit,
+ true,
+ lastExit.color || pathColor
+ );
series.push(...exitNodes);
}
});
});
-
+
// 处理其他线(保持原有逻辑)
otherLineList.forEach((line: any) => {
line.forEach((item: any) => {
@@ -948,10 +1096,10 @@ export const WorldGeo = memo(
}
});
});
-
+
return true;
};
-
+
// 创建A点和B点,并添加飞线和标签
const createSpecialPoints = (series: echarts.SeriesOption[]) => {
// 定义点A和点B的坐标
@@ -1094,7 +1242,7 @@ export const WorldGeo = memo(
});
return series;
};
-
+
const getOption = () => {
const series: echarts.SeriesOption[] = [];
getLianData(series);
@@ -1184,7 +1332,7 @@ export const WorldGeo = memo(
};
return option;
};
-
+
// 创建DOM标签
const createDOMLabels = () => {
// 清除现有标签
@@ -1250,7 +1398,7 @@ export const WorldGeo = memo(
// 更新标签位置
updateLabelPositions();
};
-
+
// 更新标签位置
const updateLabelPositions = () => {
if (!proxyGeoRef.current || !labelContainerRef.current) return;
@@ -1267,19 +1415,21 @@ export const WorldGeo = memo(
}
});
};
-
+
const handleResize = () => {
proxyGeoRef.current?.resize();
updateLabelPositions();
- positionCustomTooltip();
+ if (tooltipClosed) {
+ positionCustomTooltip();
+ }
};
-
+
// 更新图表
useEffect(() => {
const option = getOption();
proxyGeoRef.current?.setOption(option);
}, [nestedEncryptionLineIndex, dynamicRouteLineIndex, allPoints]); // 当连线索引或点变化时更新图表
-
+
useEffect(() => {
lineMidpointsRef.current = []; // 重置中点数据
const option = getOption();
@@ -1287,7 +1437,7 @@ export const WorldGeo = memo(
// 创建DOM标签
setTimeout(createDOMLabels, 100);
}, [nestedEncryption, dynamicRouteGeneration, passAuthentication]);
-
+
useEffect(() => {
const chartDom = document.getElementById("screenGeo");
proxyGeoRef.current = echarts.init(chartDom);
@@ -1295,13 +1445,13 @@ export const WorldGeo = memo(
"world",
worldGeoJson as unknown as Parameters[1]
);
-
+
// 初始化时提取所有点
const initialPoints = extractAllPoints();
if (initialPoints.length > 0) {
setAllPoints(initialPoints);
}
-
+
const option = getOption();
option && proxyGeoRef.current?.setOption(option);
// 添加地图交互事件监听器
@@ -1321,19 +1471,27 @@ export const WorldGeo = memo(
proxyGeoRef.current = null;
};
}, []);
-
+
+ // 在地图初始化后定位tooltip
useEffect(() => {
- createCustomTooltip();
- return () => {
- customTooltipRef.current?.remove();
- customTooltipRef.current = null;
- };
- }, []);
-
+ if (tooltipClosed) {
+ positionCustomTooltip();
+ }
+ }, [tooltipClosed, nestedEncryption]);
+
return (
+ {tooltipClosed && (
+
+ )}
);
}
-);
\ No newline at end of file
+);
diff --git a/src/pages/anti-forensics-forwarding/index.scss b/src/pages/anti-forensics-forwarding/index.scss
index ad5969c..dcb5059 100644
--- a/src/pages/anti-forensics-forwarding/index.scss
+++ b/src/pages/anti-forensics-forwarding/index.scss
@@ -81,11 +81,24 @@
font-weight: 500;
// line-height: 24px;
}
-
-.tip-box {
+.tip-box-left{
position: relative;
- width: 626px;
- height: 281px;
+ width: 600px;
+ height: 400px;
+ 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);
+}
+.tip-box-hx,tip-box-left {
+ position: relative;
+ width: 600px;
+ height: 400px;
+ margin-left: 312.221px;
+ // min-height: 200px;
+ // max-height: 600px;
padding: 20.85px 20.353px;
background: rgba(0, 11.82, 33.10, 0.10);
border-radius: 8px;
@@ -93,7 +106,8 @@
outline-offset: -0.46px;
backdrop-filter: blur(5.50px);
- .close-icon , .close-icon2 {
+ .close-icon,
+ .close-icon2 {
width: 16px;
height: 16px;
position: absolute;
@@ -116,7 +130,7 @@
margin-left: 16px;
}
- .traffic-obfuscation-img{
+ .traffic-obfuscation-img {
width: 597px;
height: 241px;
margin-left: 16px;
@@ -127,85 +141,18 @@
position: relative;
display: flex;
- .line-img {
+ .line-img-hx {
width: 312.221px;
+ // margin-top: 80px;
+ top: 80px;
+ left: 0px;
+ position: absolute;
}
- .line-img-left{
+ .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-forensics-forwarding/index.tsx b/src/pages/anti-forensics-forwarding/index.tsx
index ef7d510..8c0a3b9 100644
--- a/src/pages/anti-forensics-forwarding/index.tsx
+++ b/src/pages/anti-forensics-forwarding/index.tsx
@@ -178,13 +178,7 @@ const DecentralizedElasticNetwork = () => {
let istrue = useRef(false);
const [nestedEncryption, setNestedEncryption] = useState([]);
const [dynamicRouteGeneration, setDynamicRouteGeneration] = useState([]);
- // const [dataInfo, setDataInfo] = useState({
- // passAuthentication: {
- // ...PASS_AUTHENTICATION,
- // },
- // nestedEncryption: [NESTED_ENCRYPTION],
- // dynamicRouteGeneration: DYNAMIC_ROUTE_GENERATOR,
- // });
+ const [logs, setLogs] = useState([]);
const initData = async () => {
try {
@@ -194,6 +188,7 @@ const DecentralizedElasticNetwork = () => {
nestedEncryption.data.isLine = false;
dynamicRouteGeneration.data[0].isLine = false;
setNestedEncryption([nestedEncryption.data]);
+ setLogs(nestedEncryption.logs);
setDynamicRouteGeneration(dynamicRouteGeneration.data);
setDataInfo({
nestedEncryption: [nestedEncryption.data],
@@ -231,6 +226,7 @@ const DecentralizedElasticNetwork = () => {
void;
+ tooltipRef: React.RefObject;
+ title: string;
+ imageSrc: string;
+}) => {
+ const [visibleLogs, setVisibleLogs] = useState([]);
+ const [isComplete, setIsComplete] = useState(false);
+
+ // 过滤掉空日志
+ const filteredLogs = useMemo(() => {
+ return logs.filter((log) => log && log.trim() !== "");
+ }, [logs]);
+
+ // 使用useEffect实现逐条显示日志的效果
+ useEffect(() => {
+ if (!filteredLogs || filteredLogs.length === 0) return;
+
+ // 重置状态
+ setVisibleLogs([]);
+ setIsComplete(false);
+
+ // 先显示第一条日志
+ setVisibleLogs([filteredLogs[0]]);
+
+ // 如果只有一条日志,直接设置完成
+ if (filteredLogs.length === 1) {
+ setIsComplete(true);
+ return;
+ }
+
+ // 从第二条日志开始,每500毫秒显示一条
+ let currentIndex = 1;
+
+ const timer = setInterval(() => {
+ if (currentIndex < filteredLogs.length) {
+ setVisibleLogs((prev) => [...prev, filteredLogs[currentIndex]]);
+ currentIndex++;
+
+ // 如果已经是最后一条,设置完成状态
+ if (currentIndex >= filteredLogs.length) {
+ clearInterval(timer);
+ setIsComplete(true);
+ }
+ } else {
+ clearInterval(timer);
+ setIsComplete(true);
+ }
+ }, 500);
+
+ // 清理函数
+ return () => {
+ clearInterval(timer);
+ };
+ }, [filteredLogs]); // 当过滤后的日志变化时重新开始动画
+
+ // 自动滚动到最新的日志
+ const logsContainerRef = useRef(null);
+
+ useEffect(() => {
+ if (logsContainerRef.current && visibleLogs.length > 0) {
+ logsContainerRef.current.scrollTop =
+ logsContainerRef.current.scrollHeight;
+ }
+ }, [visibleLogs]);
+
+ // 添加调试日志
+ useEffect(() => {
+ console.log("CustomTooltip rendered", { title, logs: filteredLogs.length });
+ }, []);
+
+ return (
+
+ );
+};
+
+// 创建左侧自定义提示框组件
+const CustomTooltipLeft = ({
+ logs = [],
+ onClose,
+ tooltipRef,
+ title,
+ imageSrc,
+}: {
+ logs?: string[];
+ onClose: () => void;
+ tooltipRef: React.RefObject;
+ title: string;
+ imageSrc: string;
+}) => {
+ const [visibleLogs, setVisibleLogs] = useState([]);
+ const [isComplete, setIsComplete] = useState(false);
+
+ // 过滤掉空日志
+ const filteredLogs = useMemo(() => {
+ return logs.filter((log) => log && log.trim() !== "");
+ }, [logs]);
+
+ // 使用useEffect实现逐条显示日志的效果
+ useEffect(() => {
+ if (!filteredLogs || filteredLogs.length === 0) return;
+
+ // 重置状态
+ setVisibleLogs([]);
+ setIsComplete(false);
+
+ // 先显示第一条日志
+ setVisibleLogs([filteredLogs[0]]);
+
+ // 如果只有一条日志,直接设置完成
+ if (filteredLogs.length === 1) {
+ setIsComplete(true);
+ return;
+ }
+
+ // 从第二条日志开始,每500毫秒显示一条
+ let currentIndex = 1;
+
+ const timer = setInterval(() => {
+ if (currentIndex < filteredLogs.length) {
+ setVisibleLogs((prev) => [...prev, filteredLogs[currentIndex]]);
+ currentIndex++;
+
+ // 如果已经是最后一条,设置完成状态
+ if (currentIndex >= filteredLogs.length) {
+ clearInterval(timer);
+ setIsComplete(true);
+ }
+ } else {
+ clearInterval(timer);
+ setIsComplete(true);
+ }
+ }, 500);
+
+ // 清理函数
+ return () => {
+ clearInterval(timer);
+ };
+ }, [filteredLogs]); // 当过滤后的日志变化时重新开始动画
+
+ // 自动滚动到最新的日志
+ const logsContainerRef = useRef(null);
+
+ useEffect(() => {
+ if (logsContainerRef.current && visibleLogs.length > 0) {
+ logsContainerRef.current.scrollTop =
+ logsContainerRef.current.scrollHeight;
+ }
+ }, [visibleLogs]);
+
+ // 添加调试日志
+ useEffect(() => {
+ console.log("CustomTooltipLeft rendered", {
+ title,
+ logs: filteredLogs.length,
+ });
+ }, []);
+
+ return (
+
+ );
+};
+
// 创建单个国家的涟漪效果
const createCountryRipple = (countryCode: string, color?: string) => {
const coords = geoCoordMap[countryCode];
@@ -28,6 +337,7 @@ const createCountryRipple = (countryCode: string, color?: string) => {
color: color, // 添加颜色属性
};
};
+
export const WorldGeo = memo(
({
dataInfo,
@@ -59,6 +369,40 @@ export const WorldGeo = memo(
>([]);
const labelContainerRef = useRef(null);
const labelsRef = useRef([]);
+
+ // 添加状态来控制是否显示tooltip
+ const [showTooltip1, setShowTooltip1] = useState(false);
+ const [showTooltip2, setShowTooltip2] = useState(false);
+
+ // 模拟日志数据
+ const [nestedEncryptionLogs] = useState([
+ "初始化嵌套加密...",
+ "生成密钥对...",
+ "应用第一层加密...",
+ "应用第二层加密...",
+ "应用第三层加密...",
+ "加密完成,准备传输...",
+ ]);
+
+ const [trafficObfuscationLogs] = useState([
+ "初始化流量混淆...",
+ "分析流量特征...",
+ "应用随机填充...",
+ "调整数据包时间间隔...",
+ "模拟HTTP流量...",
+ "混淆完成,准备传输...",
+ ]);
+
+ // 添加调试日志
+ useEffect(() => {
+ console.log("Tooltip state:", {
+ tooltipClosed,
+ tooltipType,
+ showTooltip1,
+ showTooltip2,
+ });
+ }, [tooltipClosed, tooltipType, showTooltip1, showTooltip2]);
+
const mainToData = useMemo(() => {
const newList = [
dataInfo.passAuthentication,
@@ -71,7 +415,7 @@ export const WorldGeo = memo(
selectedApp && selectedApp ? [...newList, selectedApp] : newList ?? [];
// 初始化数据数组 - 不再包含 startCountry
const data: any = [];
- console.log(proxiesList,'proxiesList')
+ console.log(proxiesList, "proxiesList");
// 遍历代理列表
proxiesList.forEach((proxyItem: any) => {
// 检查是否有数据数组
@@ -107,165 +451,96 @@ export const WorldGeo = memo(
});
return data;
}, [dataInfo, selectedApp]);
- // 创建自定义提示框DOM元素
- const createCustomTooltip = () => {
- console.log("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 = () => {
+ console.log("Positioning tooltip1", {
+ hasRef: !!customTooltipRef.current,
+ hasChart: !!proxyGeoRef.current,
+ });
+
if (!customTooltipRef.current || !proxyGeoRef.current) return;
- // 找到US点
+
+ // 找到点
const coords = geoCoordMap[dataInfo.nestedEncryption?.[0]?.code ?? "GL"];
+ console.log("Tooltip1 coords:", coords);
+
if (!coords) return;
+
try {
// 将地理坐标转换为屏幕坐标
const screenCoord = proxyGeoRef.current.convertToPixel("geo", coords);
+ console.log("Tooltip1 screen coords:", screenCoord);
+
if (
screenCoord &&
Array.isArray(screenCoord) &&
screenCoord.length === 2
) {
// 设置提示框位置
- customTooltipRef.current.style.left = `${screenCoord[0] + 232 + 7}px`;
- customTooltipRef.current.style.top = `${screenCoord[1] + 40 + 15}px`;
+ const left = `${screenCoord[0] + 232 + 7}px`;
+ const top = `${screenCoord[1] + 40 + 15}px`;
+ console.log("Setting tooltip1 position:", { left, top });
+
+ customTooltipRef.current.style.left = left;
+ customTooltipRef.current.style.top = top;
}
} catch (error) {
- console.error("Error positioning tooltip:", error);
+ console.error("Error positioning tooltip1:", 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-icon2");
- if (closeButton) {
- closeButton.addEventListener("click", () => {
- setTooltipClosed(false);
- customTooltip2Ref.current?.remove();
- customTooltip2Ref.current = null;
- });
- }
- // 定位提示框
- positionCustomTooltip2();
- };
- // 定位自定义提示框 - 优化版本
+
+ // 定位自定义提示框2 - 优化版本
const positionCustomTooltip2 = () => {
+ console.log("Positioning tooltip2", {
+ hasRef: !!customTooltip2Ref.current,
+ hasChart: !!proxyGeoRef.current,
+ });
+
if (!customTooltip2Ref.current || !proxyGeoRef.current) return;
- // 找到US点
+
+ // 找到点
const coords =
geoCoordMap[dataInfo.trafficObfuscation?.[0]?.code ?? "ZA"];
+ console.log("Tooltip2 coords:", coords);
+
if (!coords) return;
+
try {
// 将地理坐标转换为屏幕坐标
const screenCoord = proxyGeoRef.current.convertToPixel("geo", coords);
+ console.log("Tooltip2 screen coords:", screenCoord);
+
if (
screenCoord &&
Array.isArray(screenCoord) &&
screenCoord.length === 2
) {
// 设置提示框位置
- customTooltip2Ref.current.style.left = `${
- screenCoord[0] - 626 + 20
- }px`;
- customTooltip2Ref.current.style.top = `${
- screenCoord[1] + 40 - 13
- }px`;
+ const left = `${screenCoord[0] - 626 + 20}px`;
+ const top = `${screenCoord[1] + 40 - 13}px`;
+ console.log("Setting tooltip2 position:", { left, top });
+
+ customTooltip2Ref.current.style.left = left;
+ customTooltip2Ref.current.style.top = top;
}
} catch (error) {
- console.error("Error positioning tooltip:", error);
+ console.error("Error positioning tooltip2:", error);
}
};
+
+ // 处理关闭tooltip
+ const handleCloseTooltip1 = () => {
+ setShowTooltip1(false);
+ setTooltipClosed(false);
+ };
+
+ // 处理关闭tooltip2
+ const handleCloseTooltip2 = () => {
+ setShowTooltip2(false);
+ setTooltipClosed(false);
+ };
+
const getLineItem = (
preCode: string,
nextCode: string,
@@ -286,6 +561,7 @@ export const WorldGeo = memo(
},
];
};
+
const getLine = () => {
// 实现数据处理
const solidData: LinesType[] = [["main", []]]; // 使用"main"替代startCountry.country_code
@@ -345,7 +621,11 @@ export const WorldGeo = memo(
}
// 检查是否应该绘制连线
if (currentItem.isLine !== false) {
- const lineItem = getLineItem(countryCode, nextCountryCode, lineColor);
+ const lineItem = getLineItem(
+ countryCode,
+ nextCountryCode,
+ lineColor
+ );
solidData[0][1].push(lineItem);
}
}
@@ -359,6 +639,7 @@ export const WorldGeo = memo(
pointColors,
};
};
+
// 获取连线经纬度数据
const convertData = (data: LinesDataType[]) => {
const res = [];
@@ -371,7 +652,7 @@ export const WorldGeo = memo(
const toCountry = dataIndex?.[1]?.country_code ?? "";
// 使用每条线自己的颜色
const lineColor = dataIndex?.[0]?.color || "#0ea5e9";
-
+
if (fromCoord && toCoord) {
res.push({
coords: [fromCoord, toCoord],
@@ -379,7 +660,7 @@ export const WorldGeo = memo(
color: lineColor, // 使用自定义颜色
},
// 保存颜色信息用于飞行特效
- color: lineColor
+ color: lineColor,
});
// 计算中点,考虑曲线的弧度
const curveness = -0.4; // 与飞线弧度相同
@@ -405,6 +686,7 @@ export const WorldGeo = memo(
// lineMidpointsRef.current = midpoints;
return res;
};
+
// 创建双层点效果 - 大点
const createDualLayerPoint = (
lastExit: LinesItemType,
@@ -412,7 +694,7 @@ export const WorldGeo = memo(
) => {
// 使用点自己的颜色
const pointColor = lastExit.color || "#0ea5e9";
-
+
// 创建数据数组,用于两个散点图层
const pointData = lastExit
? [lastExit].map((v) => {
@@ -421,7 +703,7 @@ export const WorldGeo = memo(
value: v.value,
datas: {
country_code: v.country_code,
- color: pointColor // 保存颜色信息
+ color: pointColor, // 保存颜色信息
},
};
})
@@ -437,9 +719,9 @@ export const WorldGeo = memo(
coordinateSystem: "geo",
zlevel: 3,
itemStyle: {
- color: function(params: any) {
+ color: function (params: any) {
return params.data.datas.color;
- }
+ },
},
symbol: "circle",
symbolSize: outerSize,
@@ -493,6 +775,7 @@ export const WorldGeo = memo(
} as echarts.SeriesOption,
];
};
+
// 添加新方法:根据经纬度数组创建蓝色涟漪小点(不包含白色内层点)
const createRipplePointsFromCoordinates = (
coordinates: [number, number][],
@@ -500,7 +783,7 @@ export const WorldGeo = memo(
color: string = "#01FF5E"
) => {
if (!coordinates || coordinates.length === 0) return;
-
+
// 只创建外层带涟漪效果的点
series.push({
type: "effectScatter",
@@ -524,6 +807,7 @@ export const WorldGeo = memo(
})),
} as echarts.SeriesOption);
};
+
// 创建路径点的双层效果
const createPathPoints = (
dataItems: LinesDataType[],
@@ -533,13 +817,13 @@ export const WorldGeo = memo(
const pointData = dataItems.map((dataItem: LinesDataType) => {
// 使用每个点自己的颜色
const pointColor = dataItem[0].color || "#0ea5e9";
-
+
return {
name: dataItem[0].name,
value: geoCoordMap[dataItem[0].country_code],
datas: {
country_code: dataItem[0].country_code,
- color: pointColor // 保存颜色信息
+ color: pointColor, // 保存颜色信息
},
};
});
@@ -606,6 +890,7 @@ export const WorldGeo = memo(
} as echarts.SeriesOption,
];
};
+
// 创建带自定义提示框的涟漪点
const createRipplePointsWithTooltip = (ripplePoints: any) => {
return {
@@ -662,6 +947,7 @@ export const WorldGeo = memo(
})),
} as echarts.SeriesOption;
};
+
// 连线 series
const getLianData = (series: echarts.SeriesOption[]) => {
const { solidData, otherLineList, ripplePoints } = getLine();
@@ -694,13 +980,13 @@ export const WorldGeo = memo(
if (item[1].length === 0) {
return;
}
-
+
// 处理每条线段
item[1].forEach((lineSegment, index) => {
const fromPoint = lineSegment[0];
const toPoint = lineSegment[1];
const lineColor = fromPoint.color || "#0ea5e9";
-
+
// 添加单条飞行线
series.push({
name: `${item[0]}-${index}`,
@@ -726,13 +1012,15 @@ export const WorldGeo = memo(
opacity: 0.1,
color: lineColor, // 使用线段自己的颜色
},
- data: convertData([[fromPoint, toPoint]]) as echarts.LinesSeriesOption["data"],
+ data: convertData([
+ [fromPoint, toPoint],
+ ]) as echarts.LinesSeriesOption["data"],
});
-
+
// 添加起点的双层效果
const startNodes = createDualLayerPoint(fromPoint, true);
series.push(...startNodes);
-
+
// 如果是最后一个线段,添加终点的双层效果
if (index === item[1].length - 1) {
const endNodes = createDualLayerPoint(toPoint, true);
@@ -740,7 +1028,7 @@ export const WorldGeo = memo(
}
});
});
-
+
otherLineList.forEach((line: any) => {
line.forEach((item: any) => {
// 处理每条虚线段
@@ -748,7 +1036,7 @@ export const WorldGeo = memo(
const fromPoint = lineSegment[0];
const toPoint = lineSegment[1];
const lineColor = fromPoint.color || "#F0FFA2";
-
+
// 添加虚线
series.push({
name: `${item[0]}-dashed-${index}`,
@@ -765,13 +1053,15 @@ export const WorldGeo = memo(
width: 0.5, // 飞线宽度
opacity: 0.6,
},
- data: convertData([[fromPoint, toPoint]]) as echarts.LinesSeriesOption["data"],
+ data: convertData([
+ [fromPoint, toPoint],
+ ]) as echarts.LinesSeriesOption["data"],
});
-
+
// 添加起点的双层效果
const startNodes = createDualLayerPoint(fromPoint, false);
series.push(...startNodes);
-
+
// 如果是最后一个线段,添加终点的双层效果
if (index === item[1].length - 1) {
const endNodes = createDualLayerPoint(toPoint, false);
@@ -780,9 +1070,10 @@ export const WorldGeo = memo(
});
});
});
-
+
return true;
};
+
// 创建A点和B点,并添加飞线和标签
const createSpecialPoints = (series: echarts.SeriesOption[]) => {
// 定义点A和点B的坐标
@@ -926,6 +1217,7 @@ export const WorldGeo = memo(
});
return series;
};
+
const getOption = () => {
const series: echarts.SeriesOption[] = [];
getLianData(series);
@@ -1015,6 +1307,7 @@ export const WorldGeo = memo(
};
return option;
};
+
// 创建DOM标签
const createDOMLabels = () => {
// 清除现有标签
@@ -1079,6 +1372,7 @@ export const WorldGeo = memo(
// 更新标签位置
updateLabelPositions();
};
+
// 更新标签位置
const updateLabelPositions = () => {
if (!proxyGeoRef.current || !labelContainerRef.current) return;
@@ -1095,10 +1389,20 @@ export const WorldGeo = memo(
}
});
};
+
const handleResize = () => {
proxyGeoRef.current?.resize();
updateLabelPositions();
+
+ // 重新定位tooltip
+ if (showTooltip1) {
+ positionCustomTooltip();
+ }
+ if (showTooltip2) {
+ positionCustomTooltip2();
+ }
};
+
useEffect(() => {
preMainToData.current?.some(
(item, index) => item.country_code !== mainToData[index]?.country_code
@@ -1109,6 +1413,7 @@ export const WorldGeo = memo(
// 创建DOM标签
setTimeout(createDOMLabels, 100);
}, [dataInfo, mainToData]);
+
useEffect(() => {
const chartDom = document.getElementById("screenGeo");
proxyGeoRef.current = echarts.init(chartDom);
@@ -1135,27 +1440,81 @@ export const WorldGeo = memo(
proxyGeoRef.current = null;
};
}, []);
+
+ // 修改处理tooltip的显示和隐藏的逻辑
useEffect(() => {
+ console.log("Tooltip effect triggered:", { tooltipClosed, tooltipType });
+
if (tooltipClosed) {
- createCustomTooltip();
- createCustomTooltip2();
+ if (tooltipType === "NESTED_ENCRYPTION") {
+ setShowTooltip1(true);
+ setShowTooltip2(false); // 确保另一个是关闭的
+ // 在下一个渲染周期后定位tooltip
+ setTimeout(() => {
+ positionCustomTooltip();
+ }, 0);
+ } else if (tooltipType === "TRAFFIC_OBFUSCATION") {
+ setShowTooltip1(false); // 确保另一个是关闭的
+ setShowTooltip2(true);
+ // 在下一个渲染周期后定位tooltip
+ setTimeout(() => {
+ positionCustomTooltip2();
+ }, 0);
+ }
+ } else {
+ setShowTooltip1(false);
+ setShowTooltip2(false);
}
- return () => {
- customTooltipRef.current?.remove();
- customTooltip2Ref.current?.remove();
- customTooltipRef.current = null;
- customTooltip2Ref.current = null;
- };
}, [
tooltipClosed,
tooltipType,
dataInfo.nestedEncryption,
dataInfo.trafficObfuscation,
]);
+
+ // 在地图初始化后定位tooltip
+ useEffect(() => {
+ positionCustomTooltip();
+ positionCustomTooltip2();
+ }, [showTooltip1, showTooltip2]);
+
return (
+
+ {/* 嵌套加密提示框 */}
+
+
+
+ {/* 流量混淆提示框 - 确保条件正确 */}
+
+
+
+
);
}
-);
\ No newline at end of file
+);
+
+// 添加CSS样式
+// 可以放在你的全局CSS文件中
+// @keyframes fadeIn {
+// from { opacity: 0; transform: translateY(5px); }
+// to { opacity: 1; transform: translateY(0); }
+// }
+//
+// .animate-fadeIn {
+// animation: fadeIn 0.3s ease-out forwards;
+// }
diff --git a/src/pages/proxies/index.scss b/src/pages/proxies/index.scss
index f0f1892..46480e1 100644
--- a/src/pages/proxies/index.scss
+++ b/src/pages/proxies/index.scss
@@ -1,39 +1,5 @@
-.proxies {
- // .proxies-container {
- // &_country {
- // padding: 16px;
- // border-radius: 8px;
- // border: 1px solid #DCDFEA;
- // background: #FFF;
- // // box-shadow: 0px 1px 3px 0px rgba(16, 24, 40, 0.10), 0px 1px 2px 0px rgba(16, 24, 40, 0.06);
- // }
-
- // &::-webkit-scrollbar {
- // width: 0px;
- // height: 0px;
- // /* background-color: red; */
- // }
-
- // &::-webkit-scrollbar-thumb {
- // border-radius: 15px;
- // background-color: rgba(144, 147, 153, 0.3);
- // }
-
- // &::-webkit-scrollbar-thumb:hover {
- // background-color: rgba(144, 147, 153, 0.5);
- // }
-
- // & {
- // /* Firefox */
- // scrollbar-width: none;
- // /* auto, thin, none */
- // scrollbar-color: rgba(144, 147, 153, 0.5);
- // }
- // }
-
-
-}
+.proxies {}
.custom-font {
font-family: Arial, sans-serif;
-}
+}
\ No newline at end of file