fix:慢点连线
This commit is contained in:
parent
bab441979f
commit
a9d30c05c8
@ -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 * as echarts from "echarts";
|
||||||
// import 'echarts-gl';
|
// import 'echarts-gl';
|
||||||
// import { useQueryClient } from "@tanstack/react-query";
|
// 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 worldGeoJson from "@/assets/echarts-map/json/world.json";
|
||||||
import { geoCoordMap, countryNameMap, countryCodeMap } from "@/data";
|
import { geoCoordMap, countryNameMap, countryCodeMap } from "@/data";
|
||||||
import { getUrl } from "@/lib/utils";
|
import { getUrl } from "@/lib/utils";
|
||||||
import { CONST_TOOLTIP_TYPE } from "@/pages/anti-forensics-forwarding";
|
// 连线动画的间隔时间(毫秒)
|
||||||
const planePathImg =
|
const LINE_ANIMATION_INTERVAL = 500; // 3秒
|
||||||
"image://data:image/svg+xml;charset=utf-8;base64,PHN2ZyB3aWR0aD0iNjciIGhlaWdodD0iMTAyIiB2aWV3Qm94PSIwIDAgNjcgMTAyIiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8ZyBmaWx0ZXI9InVybCgjZmlsdGVyMF9mXzYxMTdfMjEyNDA3KSI+CjxwYXRoIGQ9Ik0zNC4yMTA5IDkxLjE4ODZMNTMuNjU3OCA0MC45NThDNTQuOTM4IDM3LjY1MTMgNTUuNzk4MyAzNC4xNTkyIDU1LjM1NjMgMzAuNjQxQzU0LjQzNTcgMjMuMzEyOCA1MC40Njg0IDExLjAyMDggMzQuMjExMiAxMS4wMjA4QzE5LjE5MDMgMTEuMDIwOCAxMy45MTEgMjEuNTE0NiAxMi4wNTU0IDI4Ljg5MTJDMTAuOTAxIDMzLjQ4MDYgMTEuOTkyNiAzOC4yMTg2IDEzLjgyMzEgNDIuNTgyN0wzNC4yMTA5IDkxLjE4ODZaIiBmaWxsPSJ1cmwoI3BhaW50MF9saW5lYXJfNjExN18yMTI0MDcpIi8+CjwvZz4KPGRlZnM+CjxmaWx0ZXIgaWQ9ImZpbHRlcjBfZl82MTE3XzIxMjQwNyIgeD0iMC44OTE3NDQiIHk9IjAuMzMxOTkiIHdpZHRoPSI2NS4yNzA3IiBoZWlnaHQ9IjEwMS41NDUiIGZpbHRlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIj4KPGZlRmxvb2QgZmxvb2Qtb3BhY2l0eT0iMCIgcmVzdWx0PSJCYWNrZ3JvdW5kSW1hZ2VGaXgiLz4KPGZlQmxlbmQgbW9kZT0ibm9ybWFsIiBpbj0iU291cmNlR3JhcGhpYyIgaW4yPSJCYWNrZ3JvdW5kSW1hZ2VGaXgiIHJlc3VsdD0ic2hhcGUiLz4KPGZlR2F1c3NpYW5CbHVyIHN0ZERldmlhdGlvbj0iNS4zNDQ0MSIgcmVzdWx0PSJlZmZlY3QxX2ZvcmVncm91bmRCbHVyXzYxMTdfMjEyNDA3Ii8+CjwvZmlsdGVyPgo8bGluZWFyR3JhZGllbnQgaWQ9InBhaW50MF9saW5lYXJfNjExN18yMTI0MDciIHgxPSIzNS4yODI2IiB5MT0iMTAuODU2NCIgeDI9IjM1LjI4MjYiIHkyPSI4Ni44NTY0IiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CjxzdG9wIHN0b3AtY29sb3I9IiMwMEYyRkYiLz4KPHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjMTUwMEZGIi8+CjwvbGluZWFyR3JhZGllbnQ+CjwvZGVmcz4KPC9zdmc+Cg==";
|
|
||||||
interface LinesItemType {
|
interface LinesItemType {
|
||||||
name: string;
|
name: string;
|
||||||
country_code: string;
|
country_code: string;
|
||||||
@ -30,23 +29,21 @@ const createCountryRipple = (countryCode: string, color?: string) => {
|
|||||||
};
|
};
|
||||||
export const WorldGeo = memo(
|
export const WorldGeo = memo(
|
||||||
({
|
({
|
||||||
currentValue,
|
nestedEncryption,
|
||||||
newHomeProxies,
|
passAuthentication,
|
||||||
tooltipType,
|
dynamicRouteGeneration,
|
||||||
tooltipClosed,
|
|
||||||
setTooltipClosed,
|
setTooltipClosed,
|
||||||
}: {
|
}: {
|
||||||
currentValue: any;
|
nestedEncryption: any;
|
||||||
newHomeProxies: any;
|
passAuthentication: any;
|
||||||
|
dynamicRouteGeneration: any;
|
||||||
tooltipType: string;
|
tooltipType: string;
|
||||||
tooltipClosed: boolean;
|
tooltipClosed: boolean;
|
||||||
setTooltipClosed: (value: boolean) => void;
|
setTooltipClosed: (value: boolean) => void;
|
||||||
}) => {
|
}) => {
|
||||||
// const queryClient = useQueryClient()
|
|
||||||
// 嵌套加密ref
|
// 嵌套加密ref
|
||||||
const customTooltipRef = useRef<HTMLDivElement | null>(null);
|
const customTooltipRef = useRef<HTMLDivElement | null>(null);
|
||||||
// 流量混淆ref
|
// 流量混淆ref
|
||||||
const customTooltip2Ref = useRef<HTMLDivElement | null>(null);
|
|
||||||
const proxyGeoRef = useRef<EChartsType | null>(null);
|
const proxyGeoRef = useRef<EChartsType | null>(null);
|
||||||
const preMainToData = useRef<{ country_code: string }[]>([]);
|
const preMainToData = useRef<{ country_code: string }[]>([]);
|
||||||
const lineMidpointsRef = useRef<
|
const lineMidpointsRef = useRef<
|
||||||
@ -59,46 +56,405 @@ export const WorldGeo = memo(
|
|||||||
>([]);
|
>([]);
|
||||||
const labelContainerRef = useRef<HTMLDivElement | null>(null);
|
const labelContainerRef = useRef<HTMLDivElement | null>(null);
|
||||||
const labelsRef = useRef<HTMLDivElement[]>([]);
|
const labelsRef = useRef<HTMLDivElement[]>([]);
|
||||||
const mainToData = useMemo(() => {
|
|
||||||
// 使用新的数据结构
|
// 添加状态来跟踪当前显示的连线索引
|
||||||
const proxiesList = currentValue ?? [];
|
const [nestedEncryptionLineIndex, setNestedEncryptionLineIndex] = useState(-1);
|
||||||
// 初始化数据数组 - 不再包含 startCountry
|
const [dynamicRouteLineIndex, setDynamicRouteLineIndex] = useState(-1);
|
||||||
const data: any = [];
|
|
||||||
// 遍历代理列表
|
// 添加状态来存储所有连线数据
|
||||||
proxiesList.forEach((proxyItem: any) => {
|
const [nestedEncryptionLines, setNestedEncryptionLines] = useState<{from: string, to: string, color?: string}[]>([]);
|
||||||
// 检查是否有数据数组
|
const [dynamicRouteLines, setDynamicRouteLines] = useState<{from: string, to: string, color?: string}[]>([]);
|
||||||
if (proxyItem.data && Array.isArray(proxyItem.data)) {
|
|
||||||
// 遍历数据数组中的每个项目
|
// 添加状态来存储所有点
|
||||||
proxyItem.data.forEach((item: any) => {
|
const [allPoints, setAllPoints] = useState<any[]>([]);
|
||||||
// 如果有 ingress_country_code,则添加一对起点和终点
|
|
||||||
if (item.ingress_country_code) {
|
// 使用ref来跟踪动画状态,避免重新渲染
|
||||||
// 添加起点(country_code)
|
const animationTimerRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
data.push({
|
const dynamicAnimationTimerRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
country_code: item.country_code,
|
|
||||||
type: "start",
|
// 添加状态来跟踪数据是否已经变化
|
||||||
isLine: proxyItem.isLine, // 保存连线标志
|
const nestedEncryptionKeyRef = useRef<string>("");
|
||||||
color: proxyItem.color || item.color, // 添加颜色属性
|
const dynamicRouteKeyRef = useRef<string>("");
|
||||||
});
|
|
||||||
// 添加终点(ingress_country_code)
|
// 初始化时提取所有点的函数
|
||||||
data.push({
|
const extractAllPoints = () => {
|
||||||
country_code: item.ingress_country_code,
|
const points: any[] = [];
|
||||||
type: "end",
|
|
||||||
isLine: proxyItem.isLine, // 保存连线标志
|
// console.log("Extracting points from nestedEncryption:", nestedEncryption);
|
||||||
color: proxyItem.color || item.color, // 添加颜色属性
|
// console.log("Extracting points from dynamicRouteGeneration:", dynamicRouteGeneration);
|
||||||
});
|
|
||||||
|
// 从嵌套加密数据中提取点
|
||||||
|
if (nestedEncryption && Array.isArray(nestedEncryption)) {
|
||||||
|
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)) {
|
||||||
|
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)) {
|
||||||
|
points.push(toPoint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从动态路由数据中提取点
|
||||||
|
if (dynamicRouteGeneration && Array.isArray(dynamicRouteGeneration)) {
|
||||||
|
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)) {
|
||||||
|
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)) {
|
||||||
|
points.push(toPoint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Extracted points:", points);
|
||||||
|
return points;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 修改初始化逻辑,确保在数据变化时立即提取点
|
||||||
|
useEffect(() => {
|
||||||
|
// 提取所有点
|
||||||
|
const points = extractAllPoints();
|
||||||
|
if (points.length > 0) {
|
||||||
|
setAllPoints(points);
|
||||||
|
}
|
||||||
|
}, [nestedEncryption, dynamicRouteGeneration]); // 监听数据变化
|
||||||
|
|
||||||
|
// 启动嵌套加密连线动画的函数
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 开始动画
|
||||||
|
animateNextLine();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理嵌套加密数据变化
|
||||||
|
useEffect(() => {
|
||||||
|
// 清除任何现有的动画定时器
|
||||||
|
if (animationTimerRef.current) {
|
||||||
|
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}[] = [];
|
||||||
|
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)) {
|
||||||
|
points.push(fromPoint);
|
||||||
|
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)) {
|
||||||
|
points.push(toPoint);
|
||||||
|
if (!allExtractedPoints.some(p => p.country_code === toCode)) {
|
||||||
|
allExtractedPoints.push(toPoint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否需要开始连线动画
|
||||||
|
if (item.isLine === true) {
|
||||||
|
connections.push({
|
||||||
|
from: fromCode,
|
||||||
|
to: toCode,
|
||||||
|
color: item.color
|
||||||
|
});
|
||||||
|
shouldStartAnimation = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 生成当前数据的唯一键
|
||||||
|
const currentKey = JSON.stringify(nestedEncryption);
|
||||||
|
|
||||||
|
// 检查数据是否变化
|
||||||
|
if (currentKey !== nestedEncryptionKeyRef.current || shouldStartAnimation) {
|
||||||
|
nestedEncryptionKeyRef.current = currentKey;
|
||||||
|
setNestedEncryptionLines(connections);
|
||||||
|
|
||||||
|
// 如果有连线数据且需要开始动画,重置索引并启动动画
|
||||||
|
if (connections.length > 0 && shouldStartAnimation) {
|
||||||
|
setNestedEncryptionLineIndex(-1); // 重置索引
|
||||||
|
|
||||||
|
// 启动连线动画
|
||||||
|
setTimeout(() => {
|
||||||
|
startNestedEncryptionAnimation(connections);
|
||||||
|
}, 500);
|
||||||
|
} else if (!shouldStartAnimation) {
|
||||||
|
// 如果不需要连线,设置索引为-1
|
||||||
|
setNestedEncryptionLineIndex(-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新所有点
|
||||||
|
if (allExtractedPoints.length > 0) {
|
||||||
|
setAllPoints(prevPoints => {
|
||||||
|
const newPoints = [...prevPoints];
|
||||||
|
allExtractedPoints.forEach(point => {
|
||||||
|
if (!newPoints.some(p => p.country_code === point.country_code)) {
|
||||||
|
newPoints.push(point);
|
||||||
} else {
|
} else {
|
||||||
// 如果没有 ingress_country_code,只添加 country_code
|
// 更新已存在点的颜色
|
||||||
data.push({
|
const existingIndex = newPoints.findIndex(p => p.country_code === point.country_code);
|
||||||
country_code: item.country_code,
|
if (existingIndex !== -1 && point.color) {
|
||||||
isLine: proxyItem.isLine, // 保存连线标志
|
newPoints[existingIndex].color = point.color;
|
||||||
color: proxyItem.color || item.color, // 添加颜色属性
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
return newPoints;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [nestedEncryption]);
|
||||||
|
|
||||||
|
// 处理动态路由数据变化
|
||||||
|
useEffect(() => {
|
||||||
|
// 清除任何现有的动画定时器
|
||||||
|
if (dynamicAnimationTimerRef.current) {
|
||||||
|
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}[] = [];
|
||||||
|
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)) {
|
||||||
|
points.push(fromPoint);
|
||||||
|
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)) {
|
||||||
|
points.push(toPoint);
|
||||||
|
if (!allExtractedPoints.some(p => p.country_code === toCode)) {
|
||||||
|
allExtractedPoints.push(toPoint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否需要开始连线动画
|
||||||
|
if (item.isLine === true) {
|
||||||
|
connections.push({
|
||||||
|
from: fromCode,
|
||||||
|
to: toCode,
|
||||||
|
color: item.color
|
||||||
|
});
|
||||||
|
shouldStartAnimation = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 生成当前数据的唯一键
|
||||||
|
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);
|
||||||
|
}, 500);
|
||||||
|
} else if (!shouldStartAnimation) {
|
||||||
|
// 如果不需要连线,设置索引为-1
|
||||||
|
setDynamicRouteLineIndex(-1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
return data;
|
|
||||||
}, [currentValue]);
|
// 更新所有点
|
||||||
|
if (allExtractedPoints.length > 0) {
|
||||||
|
setAllPoints(prevPoints => {
|
||||||
|
const newPoints = [...prevPoints];
|
||||||
|
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);
|
||||||
|
if (existingIndex !== -1 && point.color) {
|
||||||
|
newPoints[existingIndex].color = point.color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return newPoints;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [dynamicRouteGeneration]);
|
||||||
|
|
||||||
|
// 启动动态路由连线动画的函数
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 开始动画
|
||||||
|
animateNextLine();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 组件卸载时清除定时器
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
if (animationTimerRef.current) {
|
||||||
|
clearTimeout(animationTimerRef.current);
|
||||||
|
animationTimerRef.current = null;
|
||||||
|
}
|
||||||
|
if (dynamicAnimationTimerRef.current) {
|
||||||
|
clearTimeout(dynamicAnimationTimerRef.current);
|
||||||
|
dynamicAnimationTimerRef.current = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const getLineItem = (
|
||||||
|
preCode: string,
|
||||||
|
nextCode: string,
|
||||||
|
color?: string
|
||||||
|
): [LinesItemType, LinesItemType] => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: countryCodeMap[preCode] ?? "",
|
||||||
|
value: geoCoordMap[preCode] ?? [],
|
||||||
|
country_code: preCode,
|
||||||
|
color: color,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: countryCodeMap[nextCode] ?? "",
|
||||||
|
value: geoCoordMap[nextCode] ?? [],
|
||||||
|
country_code: nextCode,
|
||||||
|
color: color,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
const getLine = () => {
|
||||||
|
// 实现数据处理
|
||||||
|
const solidData: LinesType[] = []; // 不再使用单一数组,而是分开存储
|
||||||
|
|
||||||
|
// 处理嵌套加密连线 - 放入单独的数组
|
||||||
|
if (nestedEncryptionLineIndex >= 0 && nestedEncryptionLines.length > 0) {
|
||||||
|
const nestedLines: LinesDataType[] = [];
|
||||||
|
for (let i = 0; i <= nestedEncryptionLineIndex && i < nestedEncryptionLines.length; i++) {
|
||||||
|
const connection = nestedEncryptionLines[i];
|
||||||
|
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++) {
|
||||||
|
const connection = dynamicRouteLines[i];
|
||||||
|
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元素
|
// 创建自定义提示框DOM元素
|
||||||
const createCustomTooltip = () => {
|
const createCustomTooltip = () => {
|
||||||
// 如果已经存在自定义提示框,则移除它
|
// 如果已经存在自定义提示框,则移除它
|
||||||
@ -112,10 +468,6 @@ export const WorldGeo = memo(
|
|||||||
tooltip.style.zIndex = "1000";
|
tooltip.style.zIndex = "1000";
|
||||||
tooltip.style.pointerEvents = "auto";
|
tooltip.style.pointerEvents = "auto";
|
||||||
tooltip.style.backgroundColor = "transparent";
|
tooltip.style.backgroundColor = "transparent";
|
||||||
// 设置提示框内容
|
|
||||||
const currentTooltipType =
|
|
||||||
CONST_TOOLTIP_TYPE[tooltipType as keyof typeof CONST_TOOLTIP_TYPE] ||
|
|
||||||
CONST_TOOLTIP_TYPE.NESTED_ENCRYPTION;
|
|
||||||
tooltip.innerHTML = `
|
tooltip.innerHTML = `
|
||||||
<div class="tooltip-content">
|
<div class="tooltip-content">
|
||||||
<img class="line-img" src="${getUrl(
|
<img class="line-img" src="${getUrl(
|
||||||
@ -124,25 +476,15 @@ export const WorldGeo = memo(
|
|||||||
<div class="fill"></div>
|
<div class="fill"></div>
|
||||||
<div class="tip-box">
|
<div class="tip-box">
|
||||||
<div>
|
<div>
|
||||||
<div class="label" style="color: white; font-weight: bold;">${
|
<div class="label" style="color: white; font-weight: bold;">嵌套加密</div>
|
||||||
currentTooltipType.title
|
|
||||||
}</div>
|
|
||||||
<img class="close-icon" src="${getUrl(
|
<img class="close-icon" src="${getUrl(
|
||||||
"svg/Xwhite.svg"
|
"svg/Xwhite.svg"
|
||||||
)}" alt=""
|
)}" alt=""
|
||||||
style="cursor: pointer; " />
|
style="cursor: pointer; " />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<img class="${
|
<img class="encryption-img"
|
||||||
CONST_TOOLTIP_TYPE.NESTED_ENCRYPTION.type ===
|
}" src="${getUrl("image/nested-encryption.png")}" alt="" />
|
||||||
currentTooltipType.type
|
|
||||||
? "encryption-img"
|
|
||||||
: "traffic-obfuscation-img"
|
|
||||||
}" src="${getUrl(
|
|
||||||
CONST_TOOLTIP_TYPE.NESTED_ENCRYPTION.type === currentTooltipType.type
|
|
||||||
? "image/nested-encryption.png"
|
|
||||||
: "image/traffic-obfuscation.png"
|
|
||||||
)}" alt="" />
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -162,11 +504,12 @@ export const WorldGeo = memo(
|
|||||||
// 定位提示框
|
// 定位提示框
|
||||||
positionCustomTooltip();
|
positionCustomTooltip();
|
||||||
};
|
};
|
||||||
|
|
||||||
// 定位自定义提示框 - 优化版本
|
// 定位自定义提示框 - 优化版本
|
||||||
const positionCustomTooltip = () => {
|
const positionCustomTooltip = () => {
|
||||||
if (!customTooltipRef.current || !proxyGeoRef.current) return;
|
if (!customTooltipRef.current || !proxyGeoRef.current) return;
|
||||||
// 找到US点
|
// 找到US点
|
||||||
const coords = geoCoordMap[currentValue?.[0]?.code ?? "GL"];
|
const coords = geoCoordMap[nestedEncryption?.[0]?.code ?? "GL"];
|
||||||
if (!coords) return;
|
if (!coords) return;
|
||||||
try {
|
try {
|
||||||
// 将地理坐标转换为屏幕坐标
|
// 将地理坐标转换为屏幕坐标
|
||||||
@ -184,174 +527,7 @@ export const WorldGeo = memo(
|
|||||||
console.error("Error positioning tooltip:", error);
|
console.error("Error positioning tooltip:", error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// 创建自定义提示框DOM元素
|
|
||||||
const createCustomTooltip2 = () => {
|
|
||||||
// 如果已经存在自定义提示框,则移除它
|
|
||||||
if (document.getElementById("custom-fixed-tooltip2")) {
|
|
||||||
document.getElementById("custom-fixed-tooltip2")?.remove();
|
|
||||||
}
|
|
||||||
// 创建自定义提示框
|
|
||||||
const tooltip = document.createElement("div");
|
|
||||||
tooltip.id = "custom-fixed-tooltip2";
|
|
||||||
tooltip.style.position = "fixed";
|
|
||||||
tooltip.style.zIndex = "1000";
|
|
||||||
tooltip.style.pointerEvents = "auto";
|
|
||||||
tooltip.style.backgroundColor = "transparent";
|
|
||||||
tooltip.innerHTML = `
|
|
||||||
<div class="tooltip-content">
|
|
||||||
<div class="fill-left"></div>
|
|
||||||
<div class="tip-box">
|
|
||||||
<div>
|
|
||||||
<div class="label" style="color: white; font-weight: bold;">流量混淆</div>
|
|
||||||
<img class="close-icon" src="${getUrl(
|
|
||||||
"svg/Xwhite.svg"
|
|
||||||
)}" alt=""
|
|
||||||
style="cursor: pointer; " />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<img class="traffic-obfuscation-img" src="${getUrl(
|
|
||||||
"image/traffic-obfuscation.png"
|
|
||||||
)}" alt="" />
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<img class="line-img-left" src="${getUrl(
|
|
||||||
"svg/anti-forensics-forwarding/LineLeft.svg"
|
|
||||||
)}" alt="" />
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
// 添加到DOM
|
|
||||||
document.body.appendChild(tooltip);
|
|
||||||
customTooltip2Ref.current = tooltip;
|
|
||||||
// 添加关闭按钮事件
|
|
||||||
const closeButton = tooltip.querySelector(".close-icon");
|
|
||||||
if (closeButton) {
|
|
||||||
closeButton.addEventListener("click", () => {
|
|
||||||
setTooltipClosed(false);
|
|
||||||
tooltip.remove();
|
|
||||||
customTooltip2Ref.current = null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// 定位提示框
|
|
||||||
positionCustomTooltip2();
|
|
||||||
};
|
|
||||||
// 定位自定义提示框 - 优化版本
|
|
||||||
const positionCustomTooltip2 = () => {
|
|
||||||
if (!customTooltip2Ref.current || !proxyGeoRef.current) return;
|
|
||||||
// 找到US点
|
|
||||||
const coords = geoCoordMap[currentValue?.[0]?.code ?? "ZA"];
|
|
||||||
if (!coords) return;
|
|
||||||
try {
|
|
||||||
// 将地理坐标转换为屏幕坐标
|
|
||||||
const screenCoord = proxyGeoRef.current.convertToPixel("geo", coords);
|
|
||||||
if (
|
|
||||||
screenCoord &&
|
|
||||||
Array.isArray(screenCoord) &&
|
|
||||||
screenCoord.length === 2
|
|
||||||
) {
|
|
||||||
// 设置提示框位置
|
|
||||||
customTooltip2Ref.current.style.left = `${
|
|
||||||
screenCoord[0] - 626 + 20
|
|
||||||
}px`;
|
|
||||||
customTooltip2Ref.current.style.top = `${
|
|
||||||
screenCoord[1] + 40 - 218
|
|
||||||
}px`;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error positioning tooltip:", error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const getLineItem = (
|
|
||||||
preCode: string,
|
|
||||||
nextCode: string
|
|
||||||
): [LinesItemType, LinesItemType] => {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
name: countryCodeMap[preCode] ?? "",
|
|
||||||
value: geoCoordMap[preCode] ?? [],
|
|
||||||
country_code: preCode,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: countryCodeMap[nextCode] ?? "",
|
|
||||||
value: geoCoordMap[nextCode] ?? [],
|
|
||||||
country_code: nextCode,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
};
|
|
||||||
const getLine = () => {
|
|
||||||
// 实现数据处理
|
|
||||||
const solidData: LinesType[] = [["main", []]]; // 使用"main"替代startCountry.country_code
|
|
||||||
// 收集需要显示涟漪效果的所有点(包括连线和不连线的)
|
|
||||||
const ripplePoints: any[] = [];
|
|
||||||
|
|
||||||
// 处理主路径数据
|
|
||||||
for (let i = 0; i < mainToData.length; i++) {
|
|
||||||
// 如果是最后一个元素,则跳过(因为没有下一个元素作为终点)
|
|
||||||
if (i === mainToData.length - 1) continue;
|
|
||||||
const currentItem = mainToData[i];
|
|
||||||
const nextItem = mainToData[i + 1];
|
|
||||||
|
|
||||||
// 获取当前国家代码和颜色
|
|
||||||
const countryCode = currentItem.country_code.toUpperCase();
|
|
||||||
const color = currentItem.color || "#0ea5e9"; // 获取颜色,如果没有则使用默认颜色
|
|
||||||
|
|
||||||
// 如果当前项是起点,下一项是终点
|
|
||||||
if (currentItem.type === "start" && nextItem.type === "end") {
|
|
||||||
const startCode = countryCode;
|
|
||||||
const endCode = nextItem.country_code.toUpperCase();
|
|
||||||
|
|
||||||
// 无论是否连线,都添加点的涟漪效果
|
|
||||||
const startPoint = createCountryRipple(startCode, color);
|
|
||||||
const endPoint = createCountryRipple(
|
|
||||||
endCode,
|
|
||||||
nextItem.color || color
|
|
||||||
);
|
|
||||||
if (startPoint) ripplePoints.push(startPoint);
|
|
||||||
if (endPoint) ripplePoints.push(endPoint);
|
|
||||||
|
|
||||||
// 检查是否应该绘制连线
|
|
||||||
if (currentItem.isLine !== false) {
|
|
||||||
const lineItem = getLineItem(startCode, endCode);
|
|
||||||
// 添加颜色属性
|
|
||||||
lineItem[0].color = color;
|
|
||||||
lineItem[1].color = nextItem.color || color;
|
|
||||||
solidData[0]?.[1].push(lineItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 跳过下一项,因为已经处理了
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
// 常规情况:当前项到下一项
|
|
||||||
else {
|
|
||||||
const nextCountryCode = nextItem.country_code.toUpperCase();
|
|
||||||
|
|
||||||
// 无论是否连线,都添加点的涟漪效果
|
|
||||||
const currentPoint = createCountryRipple(countryCode, color);
|
|
||||||
const nextPoint = createCountryRipple(
|
|
||||||
nextCountryCode,
|
|
||||||
nextItem.color || color
|
|
||||||
);
|
|
||||||
if (currentPoint) ripplePoints.push(currentPoint);
|
|
||||||
if (nextPoint) ripplePoints.push(nextPoint);
|
|
||||||
|
|
||||||
// 检查是否应该绘制连线
|
|
||||||
if (currentItem.isLine !== false) {
|
|
||||||
const lineItem = getLineItem(countryCode, nextCountryCode);
|
|
||||||
// 添加颜色属性
|
|
||||||
lineItem[0].color = color;
|
|
||||||
lineItem[1].color = nextItem.color || color;
|
|
||||||
solidData[0]?.[1].push(lineItem);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 虚线数据处理(保持原有逻辑)
|
|
||||||
const otherLineList: any = [];
|
|
||||||
return {
|
|
||||||
solidData,
|
|
||||||
otherLineList,
|
|
||||||
ripplePoints,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
// 获取连线经纬度数据
|
// 获取连线经纬度数据
|
||||||
const convertData = (data: LinesDataType[]) => {
|
const convertData = (data: LinesDataType[]) => {
|
||||||
const res = [];
|
const res = [];
|
||||||
@ -394,6 +570,7 @@ export const WorldGeo = memo(
|
|||||||
// lineMidpointsRef.current = midpoints;
|
// lineMidpointsRef.current = midpoints;
|
||||||
return res;
|
return res;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 创建双层点效果 - 大点
|
// 创建双层点效果 - 大点
|
||||||
const createDualLayerPoint = (
|
const createDualLayerPoint = (
|
||||||
lastExit: LinesItemType,
|
lastExit: LinesItemType,
|
||||||
@ -480,6 +657,7 @@ export const WorldGeo = memo(
|
|||||||
} as echarts.SeriesOption,
|
} as echarts.SeriesOption,
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
// 添加新方法:根据经纬度数组创建蓝色涟漪小点(不包含白色内层点)
|
// 添加新方法:根据经纬度数组创建蓝色涟漪小点(不包含白色内层点)
|
||||||
const createRipplePointsFromCoordinates = (
|
const createRipplePointsFromCoordinates = (
|
||||||
coordinates: [number, number][],
|
coordinates: [number, number][],
|
||||||
@ -511,6 +689,7 @@ export const WorldGeo = memo(
|
|||||||
})),
|
})),
|
||||||
} as echarts.SeriesOption);
|
} as echarts.SeriesOption);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 创建路径点的双层效果
|
// 创建路径点的双层效果
|
||||||
const createPathPoints = (
|
const createPathPoints = (
|
||||||
dataItems: LinesDataType[],
|
dataItems: LinesDataType[],
|
||||||
@ -593,6 +772,7 @@ export const WorldGeo = memo(
|
|||||||
} as echarts.SeriesOption,
|
} as echarts.SeriesOption,
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
// 创建带自定义提示框的涟漪点
|
// 创建带自定义提示框的涟漪点
|
||||||
const createRipplePointsWithTooltip = (ripplePoints: any) => {
|
const createRipplePointsWithTooltip = (ripplePoints: any) => {
|
||||||
return {
|
return {
|
||||||
@ -652,9 +832,11 @@ export const WorldGeo = memo(
|
|||||||
})),
|
})),
|
||||||
} as echarts.SeriesOption;
|
} as echarts.SeriesOption;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 连线 series
|
// 连线 series
|
||||||
const getLianData = (series: echarts.SeriesOption[]) => {
|
const getLianData = (series: echarts.SeriesOption[]) => {
|
||||||
const { solidData, otherLineList, ripplePoints } = getLine();
|
const { solidData, otherLineList, ripplePoints } = getLine();
|
||||||
|
|
||||||
// 如果有需要显示涟漪效果的点,添加它们
|
// 如果有需要显示涟漪效果的点,添加它们
|
||||||
if (ripplePoints.length > 0) {
|
if (ripplePoints.length > 0) {
|
||||||
// 添加带自定义提示框的外层蓝色点
|
// 添加带自定义提示框的外层蓝色点
|
||||||
@ -680,15 +862,17 @@ export const WorldGeo = memo(
|
|||||||
})),
|
})),
|
||||||
} as echarts.SeriesOption);
|
} as echarts.SeriesOption);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 处理每个连线组
|
||||||
solidData.forEach((item) => {
|
solidData.forEach((item) => {
|
||||||
// 如果没有连线数据,则跳过
|
// 如果没有连线数据,则跳过
|
||||||
if (item[1].length === 0) {
|
if (item[1].length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const lastExit = item[1]?.[item[1].length - 1]?.[1] ?? null;
|
|
||||||
// 获取当前路径的颜色
|
// 为每条连线创建飞行线
|
||||||
const pathColor = item[1]?.[0]?.[0]?.color || "#0ea5e9"; // 从第一个点获取颜色,如果没有则使用默认颜色
|
const pathColor = item[0] === "nested" ? "#0ea5e9" : "#F0FFA2"; // 根据类型设置默认颜色
|
||||||
|
|
||||||
// 添加飞行线
|
// 添加飞行线
|
||||||
series.push({
|
series.push({
|
||||||
name: item[0],
|
name: item[0],
|
||||||
@ -703,7 +887,6 @@ export const WorldGeo = memo(
|
|||||||
period: 4, // 特效动画时间
|
period: 4, // 特效动画时间
|
||||||
trailLength: 0.7, // 特效尾迹长度。取从 0 到 1 的值,数值越大尾迹越长
|
trailLength: 0.7, // 特效尾迹长度。取从 0 到 1 的值,数值越大尾迹越长
|
||||||
color: pathColor, // 特效颜色
|
color: pathColor, // 特效颜色
|
||||||
// symbol: planePathImg, // 特效图形标记
|
|
||||||
symbolSize: [10, 20],
|
symbolSize: [10, 20],
|
||||||
},
|
},
|
||||||
// 线条样式
|
// 线条样式
|
||||||
@ -716,23 +899,27 @@ export const WorldGeo = memo(
|
|||||||
},
|
},
|
||||||
data: convertData(item[1]) as echarts.LinesSeriesOption["data"],
|
data: convertData(item[1]) as echarts.LinesSeriesOption["data"],
|
||||||
});
|
});
|
||||||
|
|
||||||
// 添加路径点的双层效果
|
// 添加路径点的双层效果
|
||||||
const pathPoints = createPathPoints(item[1], true, pathColor);
|
const pathPoints = createPathPoints(item[1], true, pathColor);
|
||||||
series.push(...pathPoints);
|
series.push(...pathPoints);
|
||||||
|
|
||||||
// 添加出口节点的双层效果
|
// 添加出口节点的双层效果
|
||||||
if (lastExit) {
|
item[1].forEach(lineData => {
|
||||||
const exitNodes = createDualLayerPoint(lastExit, true, pathColor);
|
const lastExit = lineData[1];
|
||||||
series.push(...exitNodes);
|
if (lastExit) {
|
||||||
}
|
const exitNodes = createDualLayerPoint(lastExit, true, lastExit.color || pathColor);
|
||||||
|
series.push(...exitNodes);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 处理其他线(保持原有逻辑)
|
||||||
otherLineList.forEach((line: any) => {
|
otherLineList.forEach((line: any) => {
|
||||||
line.forEach((item: any) => {
|
line.forEach((item: any) => {
|
||||||
const lastExit = item[1]?.[item[1].length - 1]?.[1] ?? null;
|
const lastExit = item[1]?.[item[1].length - 1]?.[1] ?? null;
|
||||||
// 获取当前路径的颜色
|
// 获取当前路径的颜色
|
||||||
const pathColor = item[1]?.[0]?.[0]?.color || "#F0FFA2"; // 从第一个点获取颜色,如果没有则使用默认颜色
|
const pathColor = item[1]?.[0]?.[0]?.color || "#F0FFA2"; // 从第一个点获取颜色,如果没有则使用默认颜色
|
||||||
|
|
||||||
// 添加虚线
|
// 添加虚线
|
||||||
series.push({
|
series.push({
|
||||||
name: item[0],
|
name: item[0],
|
||||||
@ -761,14 +948,15 @@ export const WorldGeo = memo(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 创建A点和B点,并添加飞线和标签
|
// 创建A点和B点,并添加飞线和标签
|
||||||
const createSpecialPoints = (series: echarts.SeriesOption[]) => {
|
const createSpecialPoints = (series: echarts.SeriesOption[]) => {
|
||||||
// 定义点A和点B的坐标
|
// 定义点A和点B的坐标
|
||||||
const pointA = geoCoordMap[currentValue[0]?.startPoint ?? "GL"];
|
const pointA = geoCoordMap[passAuthentication[0]?.startPoint ?? "GL"];
|
||||||
const pointB = geoCoordMap[currentValue[0]?.endPoint ?? "CA"];
|
const pointB = geoCoordMap[passAuthentication[0]?.endPoint ?? "CA"];
|
||||||
const newPointB = [pointB[0] + 14, pointB[1] + 10];
|
const newPointB = [pointB[0] + 14, pointB[1] + 10];
|
||||||
// 添加A点 - 带涟漪效果的双层点
|
// 添加A点 - 带涟漪效果的双层点
|
||||||
series.push(
|
series.push(
|
||||||
@ -906,25 +1094,21 @@ export const WorldGeo = memo(
|
|||||||
});
|
});
|
||||||
return series;
|
return series;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getOption = () => {
|
const getOption = () => {
|
||||||
const series: echarts.SeriesOption[] = [];
|
const series: echarts.SeriesOption[] = [];
|
||||||
getLianData(series);
|
getLianData(series);
|
||||||
// getMianLineTipData(series); // 添加主线tip 暂时隐藏
|
// getMianLineTipData(series); // 添加主线tip 暂时隐藏
|
||||||
if (
|
if (
|
||||||
tooltipType === "PASS_AUTHENTICATION" &&
|
passAuthentication.length &&
|
||||||
currentValue &&
|
passAuthentication[0]?.authenticationPoint
|
||||||
currentValue.length &&
|
|
||||||
currentValue[0]?.authenticationPoint
|
|
||||||
) {
|
) {
|
||||||
console.log(currentValue, "values");
|
|
||||||
|
|
||||||
createSpecialPoints(series); // 添加特殊点和飞线
|
createSpecialPoints(series); // 添加特殊点和飞线
|
||||||
createRipplePointsFromCoordinates(
|
createRipplePointsFromCoordinates(
|
||||||
currentValue[0]?.authenticationPoint || [],
|
passAuthentication[0]?.authenticationPoint || [],
|
||||||
series
|
series
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const option = {
|
const option = {
|
||||||
backgroundColor: "transparent",
|
backgroundColor: "transparent",
|
||||||
// 全局提示框配置
|
// 全局提示框配置
|
||||||
@ -1000,6 +1184,7 @@ export const WorldGeo = memo(
|
|||||||
};
|
};
|
||||||
return option;
|
return option;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 创建DOM标签
|
// 创建DOM标签
|
||||||
const createDOMLabels = () => {
|
const createDOMLabels = () => {
|
||||||
// 清除现有标签
|
// 清除现有标签
|
||||||
@ -1065,6 +1250,7 @@ export const WorldGeo = memo(
|
|||||||
// 更新标签位置
|
// 更新标签位置
|
||||||
updateLabelPositions();
|
updateLabelPositions();
|
||||||
};
|
};
|
||||||
|
|
||||||
// 更新标签位置
|
// 更新标签位置
|
||||||
const updateLabelPositions = () => {
|
const updateLabelPositions = () => {
|
||||||
if (!proxyGeoRef.current || !labelContainerRef.current) return;
|
if (!proxyGeoRef.current || !labelContainerRef.current) return;
|
||||||
@ -1081,20 +1267,27 @@ export const WorldGeo = memo(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleResize = () => {
|
const handleResize = () => {
|
||||||
proxyGeoRef.current?.resize();
|
proxyGeoRef.current?.resize();
|
||||||
updateLabelPositions();
|
updateLabelPositions();
|
||||||
|
positionCustomTooltip();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 更新图表
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
preMainToData.current?.some(
|
const option = getOption();
|
||||||
(item, index) => item.country_code !== mainToData[index]?.country_code
|
proxyGeoRef.current?.setOption(option);
|
||||||
) && proxyGeoRef.current?.clear();
|
}, [nestedEncryptionLineIndex, dynamicRouteLineIndex, allPoints]); // 当连线索引或点变化时更新图表
|
||||||
preMainToData.current = mainToData;
|
|
||||||
|
useEffect(() => {
|
||||||
|
lineMidpointsRef.current = []; // 重置中点数据
|
||||||
const option = getOption();
|
const option = getOption();
|
||||||
proxyGeoRef.current?.setOption(option);
|
proxyGeoRef.current?.setOption(option);
|
||||||
// 创建DOM标签
|
// 创建DOM标签
|
||||||
setTimeout(createDOMLabels, 100);
|
setTimeout(createDOMLabels, 100);
|
||||||
}, [newHomeProxies, mainToData]);
|
}, [nestedEncryption, dynamicRouteGeneration, passAuthentication]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const chartDom = document.getElementById("screenGeo");
|
const chartDom = document.getElementById("screenGeo");
|
||||||
proxyGeoRef.current = echarts.init(chartDom);
|
proxyGeoRef.current = echarts.init(chartDom);
|
||||||
@ -1102,6 +1295,13 @@ export const WorldGeo = memo(
|
|||||||
"world",
|
"world",
|
||||||
worldGeoJson as unknown as Parameters<typeof echarts.registerMap>[1]
|
worldGeoJson as unknown as Parameters<typeof echarts.registerMap>[1]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 初始化时提取所有点
|
||||||
|
const initialPoints = extractAllPoints();
|
||||||
|
if (initialPoints.length > 0) {
|
||||||
|
setAllPoints(initialPoints);
|
||||||
|
}
|
||||||
|
|
||||||
const option = getOption();
|
const option = getOption();
|
||||||
option && proxyGeoRef.current?.setOption(option);
|
option && proxyGeoRef.current?.setOption(option);
|
||||||
// 添加地图交互事件监听器
|
// 添加地图交互事件监听器
|
||||||
@ -1121,34 +1321,19 @@ export const WorldGeo = memo(
|
|||||||
proxyGeoRef.current = null;
|
proxyGeoRef.current = null;
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (tooltipType !== "PASS_AUTHENTICATION") {
|
createCustomTooltip();
|
||||||
lineMidpointsRef.current = [];
|
|
||||||
}
|
|
||||||
if (tooltipClosed) {
|
|
||||||
if (tooltipType === "NESTED_ENCRYPTION") {
|
|
||||||
createCustomTooltip();
|
|
||||||
}
|
|
||||||
if (tooltipType === "TRAFFIC_OBFUSCATION") {
|
|
||||||
createCustomTooltip2();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
customTooltipRef.current?.remove();
|
|
||||||
customTooltip2Ref.current?.remove();
|
|
||||||
customTooltipRef.current = null;
|
|
||||||
customTooltip2Ref.current = null;
|
|
||||||
}
|
|
||||||
return () => {
|
return () => {
|
||||||
customTooltipRef.current?.remove();
|
customTooltipRef.current?.remove();
|
||||||
customTooltip2Ref.current?.remove();
|
|
||||||
customTooltipRef.current = null;
|
customTooltipRef.current = null;
|
||||||
customTooltip2Ref.current = null;
|
|
||||||
};
|
};
|
||||||
}, [tooltipClosed, tooltipType, currentValue]);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex-1 h-full flex flex-col">
|
<div className="flex-1 h-full flex flex-col">
|
||||||
<div id="screenGeo" className="flex-1"></div>
|
<div id="screenGeo" className="flex-1"></div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@ -21,7 +21,7 @@ export const TRAFFIC_OBFUSCATION = {
|
|||||||
ingress_country_code: "cn",
|
ingress_country_code: "cn",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
isLine: true,
|
isLine: false,
|
||||||
};
|
};
|
||||||
export const NESTED_ENCRYPTION = {
|
export const NESTED_ENCRYPTION = {
|
||||||
type: "NESTED_ENCRYPTION",
|
type: "NESTED_ENCRYPTION",
|
||||||
@ -45,34 +45,34 @@ export const NESTED_ENCRYPTION = {
|
|||||||
ingress_country_code: "cn",
|
ingress_country_code: "cn",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
isLine: true,
|
isLine: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DYNAMIC_ROUTE_GENERATOR = [
|
export const DYNAMIC_ROUTE_GENERATOR = [
|
||||||
{
|
// {
|
||||||
type: "DYNAMIC_ROUTE_GENERATOR",
|
// type: "DYNAMIC_ROUTE_GENERATOR",
|
||||||
name: "动态路由生成",
|
// name: "动态路由生成",
|
||||||
data: [
|
// data: [
|
||||||
{
|
// {
|
||||||
country_code: "us",
|
// country_code: "us",
|
||||||
ingress_country_code: "ca",
|
// ingress_country_code: "ca",
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
country_code: "ca",
|
// country_code: "ca",
|
||||||
ingress_country_code: "gl",
|
// ingress_country_code: "gl",
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
country_code: "gl",
|
// country_code: "gl",
|
||||||
ingress_country_code: "by",
|
// ingress_country_code: "by",
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
country_code: "dz",
|
// country_code: "dz",
|
||||||
ingress_country_code: "cn",
|
// ingress_country_code: "cn",
|
||||||
},
|
// },
|
||||||
],
|
// ],
|
||||||
color: "#48D3D5",
|
// color: "#48D3D5",
|
||||||
isLine: true,
|
// isLine: true,
|
||||||
},
|
// },
|
||||||
{
|
{
|
||||||
type: "DYNAMIC_ROUTE_GENERATOR",
|
type: "DYNAMIC_ROUTE_GENERATOR",
|
||||||
name: "动态路由生成2",
|
name: "动态路由生成2",
|
||||||
@ -95,7 +95,7 @@ export const DYNAMIC_ROUTE_GENERATOR = [
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
color: "#50FE35",
|
color: "#50FE35",
|
||||||
isLine: true,
|
isLine: false,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -401,7 +401,7 @@ export const APP_DIVERSION = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const PASS_AUTHENTICATION = {
|
export const PASS_AUTHENTICATION = {
|
||||||
type: "PASS_AUTHENTICATION",
|
type: "PASS_AUTHENTICATION",
|
||||||
name: "通信认证",
|
name: "通信认证",
|
||||||
startPoint: "GL",
|
startPoint: "GL",
|
||||||
@ -424,26 +424,7 @@ export const PASS_AUTHENTICATION = {
|
|||||||
[-102.346771, 68.130366],
|
[-102.346771, 68.130366],
|
||||||
],
|
],
|
||||||
data: [
|
data: [
|
||||||
{
|
|
||||||
country_code: "gl",
|
|
||||||
ingress_country_code: "dz",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
country_code: "br",
|
|
||||||
ingress_country_code: "dz",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
country_code: "dz",
|
|
||||||
ingress_country_code: "ru",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
country_code: "dz",
|
|
||||||
ingress_country_code: "cn",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
country_code: "ru",
|
|
||||||
ingress_country_code: "za",
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
isLine: true,
|
isLine: true,
|
||||||
}
|
}
|
||||||
@ -1,6 +1,4 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useSelector } from "react-redux";
|
|
||||||
import { WorldGeo } from "./components/world-geo";
|
|
||||||
|
|
||||||
import NetflixSvg from "@/assets/svg/anti-forensics-forwarding/Netflix.svg?react";
|
import NetflixSvg from "@/assets/svg/anti-forensics-forwarding/Netflix.svg?react";
|
||||||
import NetflixActiveSvg from "@/assets/svg/anti-forensics-forwarding/NetflixActive.svg?react";
|
import NetflixActiveSvg from "@/assets/svg/anti-forensics-forwarding/NetflixActive.svg?react";
|
||||||
@ -28,24 +26,20 @@ import YouTubeSvg from "@/assets/svg/anti-forensics-forwarding/YouTube.svg?react
|
|||||||
import YouTubeActiveSvg from "@/assets/svg/anti-forensics-forwarding/YouTubeActive.svg?react";
|
import YouTubeActiveSvg from "@/assets/svg/anti-forensics-forwarding/YouTubeActive.svg?react";
|
||||||
import FacebookSvg from "@/assets/svg/anti-forensics-forwarding/Facebook.svg?react";
|
import FacebookSvg from "@/assets/svg/anti-forensics-forwarding/Facebook.svg?react";
|
||||||
import FacebookActiveSvg from "@/assets/svg/anti-forensics-forwarding/FacebookActive.svg?react";
|
import FacebookActiveSvg from "@/assets/svg/anti-forensics-forwarding/FacebookActive.svg?react";
|
||||||
import { RootState } from "@/store";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
TRAFFIC_OBFUSCATION,
|
|
||||||
NESTED_ENCRYPTION,
|
NESTED_ENCRYPTION,
|
||||||
DYNAMIC_ROUTE_GENERATOR,
|
DYNAMIC_ROUTE_GENERATOR,
|
||||||
APP_DIVERSION,
|
|
||||||
PASS_AUTHENTICATION,
|
PASS_AUTHENTICATION,
|
||||||
} from "./data/mockData";
|
} from "./data/mockData";
|
||||||
|
|
||||||
import "./index.scss";
|
import "./index.scss";
|
||||||
import {
|
import {
|
||||||
getApplicationDiversion,
|
|
||||||
getDynamicRouteGeneration,
|
getDynamicRouteGeneration,
|
||||||
getNestedEncryption,
|
getNestedEncryption,
|
||||||
getPassAuthentication,
|
getPassAuthentication,
|
||||||
getTrafficObfuscation,
|
|
||||||
} from "@/api/flying-line";
|
} from "@/api/flying-line";
|
||||||
|
import { WorldGeo } from "./components/world-geo";
|
||||||
|
|
||||||
export const DIALOGTYPE = {
|
export const DIALOGTYPE = {
|
||||||
ADDNode: {
|
ADDNode: {
|
||||||
@ -167,92 +161,67 @@ export const CONST_TOOLTIP_TYPE = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const DecentralizedElasticNetwork = () => {
|
const DecentralizedElasticNetwork = () => {
|
||||||
const { newHomeProxies } = useSelector(
|
|
||||||
(state: RootState) => state.web3Reducer
|
|
||||||
);
|
|
||||||
|
|
||||||
const [tooltipType, setTooltipType] = useState(
|
const [tooltipType, setTooltipType] = useState(
|
||||||
CONST_TOOLTIP_TYPE.PASS_AUTHENTICATION.type
|
CONST_TOOLTIP_TYPE.PASS_AUTHENTICATION.type
|
||||||
);
|
);
|
||||||
const [tooltipClosed, setTooltipClosed] = useState(false);
|
const [tooltipClosed, setTooltipClosed] = useState(false);
|
||||||
|
|
||||||
const [selectedApp, setSelectedApp] = useState<any>(null);
|
const [dataInfo, setDataInfo] = useState<any>({
|
||||||
const [dataInfo, setDataInfo] = useState<any>(null);
|
passAuthentication: [
|
||||||
|
{
|
||||||
|
...PASS_AUTHENTICATION,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
nestedEncryption: [NESTED_ENCRYPTION],
|
||||||
|
dynamicRouteGeneration: DYNAMIC_ROUTE_GENERATOR,
|
||||||
|
});
|
||||||
|
let istrue = useRef(false);
|
||||||
|
const [nestedEncryption, setNestedEncryption] = useState<any>([]);
|
||||||
|
const [dynamicRouteGeneration, setDynamicRouteGeneration] = useState<any>([]);
|
||||||
|
// const [dataInfo, setDataInfo] = useState<any>({
|
||||||
|
// passAuthentication: {
|
||||||
|
// ...PASS_AUTHENTICATION,
|
||||||
|
// },
|
||||||
|
// nestedEncryption: [NESTED_ENCRYPTION],
|
||||||
|
// dynamicRouteGeneration: DYNAMIC_ROUTE_GENERATOR,
|
||||||
|
// });
|
||||||
|
|
||||||
const currentValue = useMemo(() => {
|
|
||||||
let value = dataInfo;
|
|
||||||
|
|
||||||
switch (tooltipType) {
|
|
||||||
case CONST_TOOLTIP_TYPE.APP_DIVERSION.type:
|
|
||||||
value = selectedApp ? [selectedApp] : [];
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}, [tooltipType, selectedApp,dataInfo]);
|
|
||||||
|
|
||||||
const handleClickApp = (item: any) => {
|
|
||||||
setSelectedApp(item);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getDataInfo = async () => {
|
|
||||||
let value = [];
|
|
||||||
|
|
||||||
switch (tooltipType) {
|
|
||||||
case CONST_TOOLTIP_TYPE.NESTED_ENCRYPTION.type:
|
|
||||||
const nestedEncryption = await getNestedEncryption();
|
|
||||||
value = [nestedEncryption.data];
|
|
||||||
break;
|
|
||||||
case CONST_TOOLTIP_TYPE.TRAFFIC_OBFUSCATION.type:
|
|
||||||
const trafficObfuscation = await getTrafficObfuscation();
|
|
||||||
value = [trafficObfuscation.data];
|
|
||||||
break;
|
|
||||||
case CONST_TOOLTIP_TYPE.DYNAMIC_ROUTE_GENERATOR.type:
|
|
||||||
const dynamicRouteGeneration = await getDynamicRouteGeneration();
|
|
||||||
value = dynamicRouteGeneration.data;
|
|
||||||
break;
|
|
||||||
// case CONST_TOOLTIP_TYPE.APP_DIVERSION.type:
|
|
||||||
// const applicationDiversion = await getApplicationDiversion();
|
|
||||||
|
|
||||||
// value = selectedApp ? [selectedApp] : [];
|
|
||||||
// break;
|
|
||||||
case CONST_TOOLTIP_TYPE.PASS_AUTHENTICATION.type:
|
|
||||||
const passAuthentication = await getPassAuthentication();
|
|
||||||
value = [passAuthentication.data];
|
|
||||||
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
console.log(value,'valuevalue')
|
|
||||||
setDataInfo(value);
|
|
||||||
};
|
|
||||||
|
|
||||||
const [appData, setAppData] = useState<any>([]);
|
|
||||||
const appDiversion = useMemo(() => {
|
|
||||||
return Apps.map((item) => {
|
|
||||||
const findApp = appData.find(
|
|
||||||
(appItem:any) => item.name === appItem.name
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
...item,
|
|
||||||
...findApp,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}, [appData]);
|
|
||||||
const initData = async () => {
|
const initData = async () => {
|
||||||
const applicationDiversion = await getApplicationDiversion();
|
try {
|
||||||
|
const passAuthentication = await getPassAuthentication();
|
||||||
setAppData(applicationDiversion.data);
|
const dynamicRouteGeneration = await getDynamicRouteGeneration();
|
||||||
|
const nestedEncryption = await getNestedEncryption();
|
||||||
|
nestedEncryption.data.isLine = false;
|
||||||
|
dynamicRouteGeneration.data[0].isLine = false;
|
||||||
|
setNestedEncryption([nestedEncryption.data]);
|
||||||
|
setDynamicRouteGeneration(dynamicRouteGeneration.data);
|
||||||
|
setDataInfo({
|
||||||
|
nestedEncryption: [nestedEncryption.data],
|
||||||
|
passAuthentication: [passAuthentication.data],
|
||||||
|
dynamicRouteGeneration: dynamicRouteGeneration.data,
|
||||||
|
});
|
||||||
|
} catch (error) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getNewDynamicRouteGeneration = async () => {
|
||||||
|
const res = await getDynamicRouteGeneration();
|
||||||
|
res.data[0].isLine = istrue.current;
|
||||||
|
setDynamicRouteGeneration([...res.data]);
|
||||||
|
};
|
||||||
|
|
||||||
|
let timer: any = null;
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getDataInfo();
|
timer = setInterval(() => {
|
||||||
},[tooltipType])
|
getNewDynamicRouteGeneration();
|
||||||
|
}, 5000);
|
||||||
|
return () => {
|
||||||
|
clearInterval(timer);
|
||||||
|
};
|
||||||
|
}, [dynamicRouteGeneration]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
initData();
|
initData();
|
||||||
|
|
||||||
() => {
|
() => {
|
||||||
setTooltipClosed(false);
|
setTooltipClosed(false);
|
||||||
};
|
};
|
||||||
@ -260,28 +229,11 @@ const DecentralizedElasticNetwork = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="decentralized w-full h-full flex flex-col relative">
|
<div className="decentralized w-full h-full flex flex-col relative">
|
||||||
<div className="flex items-center gap-[60px] absolute top-12 left-12 z-10">
|
|
||||||
{tooltipType === CONST_TOOLTIP_TYPE.APP_DIVERSION.type &&
|
|
||||||
appDiversion.map((item) => {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={item.name}
|
|
||||||
className="flex items-center justify-center w-16 h-16 relative rounded-[4.95px] shadow-[0px_0px_3.299999952316284px_0px_rgba(84,87,255,1.00)] outline outline-[0.50px] outline-offset-[-0.50px] outline-indigo-50/60 overflow-hidden"
|
|
||||||
onClick={() => handleClickApp(item)}
|
|
||||||
>
|
|
||||||
{selectedApp?.name === item?.name ? (
|
|
||||||
<item.activeIcon />
|
|
||||||
) : (
|
|
||||||
<item.icon />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
<div className="mt-2 w-full h-full flex-1">
|
<div className="mt-2 w-full h-full flex-1">
|
||||||
<WorldGeo
|
<WorldGeo
|
||||||
currentValue={currentValue}
|
nestedEncryption={nestedEncryption}
|
||||||
newHomeProxies={newHomeProxies}
|
passAuthentication={dataInfo.passAuthentication}
|
||||||
|
dynamicRouteGeneration={dynamicRouteGeneration}
|
||||||
tooltipType={tooltipType}
|
tooltipType={tooltipType}
|
||||||
tooltipClosed={tooltipClosed}
|
tooltipClosed={tooltipClosed}
|
||||||
setTooltipClosed={setTooltipClosed}
|
setTooltipClosed={setTooltipClosed}
|
||||||
@ -302,6 +254,8 @@ const DecentralizedElasticNetwork = () => {
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
setTooltipType(CONST_TOOLTIP_TYPE.NESTED_ENCRYPTION.type);
|
setTooltipType(CONST_TOOLTIP_TYPE.NESTED_ENCRYPTION.type);
|
||||||
setTooltipClosed(true);
|
setTooltipClosed(true);
|
||||||
|
nestedEncryption[0].isLine = true;
|
||||||
|
setNestedEncryption([...nestedEncryption]);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
嵌套加密
|
嵌套加密
|
||||||
@ -310,6 +264,9 @@ const DecentralizedElasticNetwork = () => {
|
|||||||
className="bt1 cursor-pointer"
|
className="bt1 cursor-pointer"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setTooltipType(CONST_TOOLTIP_TYPE.DYNAMIC_ROUTE_GENERATOR.type);
|
setTooltipType(CONST_TOOLTIP_TYPE.DYNAMIC_ROUTE_GENERATOR.type);
|
||||||
|
dynamicRouteGeneration[0].isLine = true;
|
||||||
|
istrue.current = true;
|
||||||
|
setDynamicRouteGeneration([...dynamicRouteGeneration]);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
动态路由生成
|
动态路由生成
|
||||||
|
|||||||
@ -21,7 +21,7 @@ type LinesDataType = [LinesItemType, LinesItemType];
|
|||||||
type LinesType = [string, LinesDataType[]];
|
type LinesType = [string, LinesDataType[]];
|
||||||
|
|
||||||
// 连线动画的间隔时间(毫秒)
|
// 连线动画的间隔时间(毫秒)
|
||||||
const LINE_ANIMATION_INTERVAL = 3000; // 3秒
|
const LINE_ANIMATION_INTERVAL = 500; // 3秒
|
||||||
|
|
||||||
// 创建单个国家的涟漪效果
|
// 创建单个国家的涟漪效果
|
||||||
const createCountryRipple = (countryCode: string) => {
|
const createCountryRipple = (countryCode: string) => {
|
||||||
@ -73,7 +73,7 @@ export const WorldGeo = memo(({ screenData }: { screenData: any }) => {
|
|||||||
// 启动连线动画
|
// 启动连线动画
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
startLineAnimation(lineConnections);
|
startLineAnimation(lineConnections);
|
||||||
}, 500);
|
}, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果 isLine 从 true 变为 false,重置连线索引
|
// 如果 isLine 从 true 变为 false,重置连线索引
|
||||||
|
|||||||
@ -13,17 +13,19 @@ interface LinesItemType {
|
|||||||
name: string;
|
name: string;
|
||||||
country_code: string;
|
country_code: string;
|
||||||
value: number[];
|
value: number[];
|
||||||
|
color?: string; // 添加颜色属性
|
||||||
}
|
}
|
||||||
type LinesDataType = [LinesItemType, LinesItemType];
|
type LinesDataType = [LinesItemType, LinesItemType];
|
||||||
type LinesType = [string, LinesDataType[]];
|
type LinesType = [string, LinesDataType[]];
|
||||||
// 创建单个国家的涟漪效果
|
// 创建单个国家的涟漪效果
|
||||||
const createCountryRipple = (countryCode: string) => {
|
const createCountryRipple = (countryCode: string, color?: string) => {
|
||||||
const coords = geoCoordMap[countryCode];
|
const coords = geoCoordMap[countryCode];
|
||||||
if (!coords) return null;
|
if (!coords) return null;
|
||||||
return {
|
return {
|
||||||
name: countryCodeMap[countryCode] ?? "",
|
name: countryCodeMap[countryCode] ?? "",
|
||||||
value: coords,
|
value: coords,
|
||||||
country_code: countryCode,
|
country_code: countryCode,
|
||||||
|
color: color, // 添加颜色属性
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
export const WorldGeo = memo(
|
export const WorldGeo = memo(
|
||||||
@ -264,21 +266,23 @@ export const WorldGeo = memo(
|
|||||||
console.error("Error positioning tooltip:", error);
|
console.error("Error positioning tooltip:", error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getLineItem = (
|
const getLineItem = (
|
||||||
preCode: string,
|
preCode: string,
|
||||||
nextCode: string
|
nextCode: string,
|
||||||
|
color: string
|
||||||
): [LinesItemType, LinesItemType] => {
|
): [LinesItemType, LinesItemType] => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
name: countryCodeMap[preCode] ?? "",
|
name: countryCodeMap[preCode] ?? "",
|
||||||
value: geoCoordMap[preCode] ?? [],
|
value: geoCoordMap[preCode] ?? [],
|
||||||
country_code: preCode,
|
country_code: preCode,
|
||||||
|
color: color, // 添加颜色属性
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: countryCodeMap[nextCode] ?? "",
|
name: countryCodeMap[nextCode] ?? "",
|
||||||
value: geoCoordMap[nextCode] ?? [],
|
value: geoCoordMap[nextCode] ?? [],
|
||||||
country_code: nextCode,
|
country_code: nextCode,
|
||||||
|
color: color, // 添加颜色属性
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
@ -289,7 +293,6 @@ export const WorldGeo = memo(
|
|||||||
const ripplePoints: any[] = [];
|
const ripplePoints: any[] = [];
|
||||||
// 收集每个点的颜色信息
|
// 收集每个点的颜色信息
|
||||||
const pointColors: Record<string, string> = {};
|
const pointColors: Record<string, string> = {};
|
||||||
|
|
||||||
// 处理主路径数据
|
// 处理主路径数据
|
||||||
for (let i = 0; i < mainToData.length; i++) {
|
for (let i = 0; i < mainToData.length; i++) {
|
||||||
// 如果是最后一个元素,则跳过(因为没有下一个元素作为终点)
|
// 如果是最后一个元素,则跳过(因为没有下一个元素作为终点)
|
||||||
@ -300,44 +303,27 @@ export const WorldGeo = memo(
|
|||||||
const countryCode = currentItem.country_code.toUpperCase();
|
const countryCode = currentItem.country_code.toUpperCase();
|
||||||
// 获取颜色信息
|
// 获取颜色信息
|
||||||
const lineColor = currentItem.color || "#0ea5e9"; // 默认颜色
|
const lineColor = currentItem.color || "#0ea5e9"; // 默认颜色
|
||||||
|
|
||||||
// 保存点的颜色信息
|
// 保存点的颜色信息
|
||||||
pointColors[countryCode] = lineColor;
|
pointColors[countryCode] = lineColor;
|
||||||
|
|
||||||
// 如果当前项是起点,下一项是终点
|
// 如果当前项是起点,下一项是终点
|
||||||
if (currentItem.type === "start" && nextItem.type === "end") {
|
if (currentItem.type === "start" && nextItem.type === "end") {
|
||||||
const startCode = countryCode;
|
const startCode = countryCode;
|
||||||
const endCode = nextItem.country_code.toUpperCase();
|
const endCode = nextItem.country_code.toUpperCase();
|
||||||
|
|
||||||
// 保存终点的颜色信息
|
// 保存终点的颜色信息
|
||||||
pointColors[endCode] = lineColor;
|
pointColors[endCode] = lineColor;
|
||||||
|
|
||||||
// 无论是否连线,都添加点的涟漪效果
|
// 无论是否连线,都添加点的涟漪效果
|
||||||
const startPoint = createCountryRipple(startCode);
|
const startPoint = createCountryRipple(startCode, lineColor);
|
||||||
const endPoint = createCountryRipple(endCode);
|
const endPoint = createCountryRipple(endCode, lineColor);
|
||||||
|
|
||||||
if (startPoint) {
|
if (startPoint) {
|
||||||
ripplePoints.push({
|
ripplePoints.push(startPoint);
|
||||||
...startPoint,
|
|
||||||
color: lineColor, // 添加颜色信息
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (endPoint) {
|
if (endPoint) {
|
||||||
ripplePoints.push({
|
ripplePoints.push(endPoint);
|
||||||
...endPoint,
|
|
||||||
color: lineColor, // 添加颜色信息
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查是否应该绘制连线
|
// 检查是否应该绘制连线
|
||||||
if (currentItem.isLine !== false) {
|
if (currentItem.isLine !== false) {
|
||||||
const lineItem = getLineItem(startCode, endCode);
|
const lineItem = getLineItem(startCode, endCode, lineColor);
|
||||||
// 添加颜色信息到连线数据
|
solidData[0][1].push(lineItem);
|
||||||
solidData[0][1].push({
|
|
||||||
...lineItem,
|
|
||||||
color: lineColor, // 添加颜色信息
|
|
||||||
} as any);
|
|
||||||
}
|
}
|
||||||
// 跳过下一项,因为已经处理了
|
// 跳过下一项,因为已经处理了
|
||||||
i++;
|
i++;
|
||||||
@ -345,36 +331,22 @@ export const WorldGeo = memo(
|
|||||||
// 常规情况:当前项到下一项
|
// 常规情况:当前项到下一项
|
||||||
else {
|
else {
|
||||||
const nextCountryCode = nextItem.country_code.toUpperCase();
|
const nextCountryCode = nextItem.country_code.toUpperCase();
|
||||||
|
const nextColor = nextItem.color || "#0ea5e9"; // 获取下一个点的颜色
|
||||||
// 保存下一个点的颜色信息
|
// 保存下一个点的颜色信息
|
||||||
pointColors[nextCountryCode] = nextItem.color || lineColor;
|
pointColors[nextCountryCode] = nextColor;
|
||||||
|
|
||||||
// 无论是否连线,都添加点的涟漪效果
|
// 无论是否连线,都添加点的涟漪效果
|
||||||
const currentPoint = createCountryRipple(countryCode);
|
const currentPoint = createCountryRipple(countryCode, lineColor);
|
||||||
const nextPoint = createCountryRipple(nextCountryCode);
|
const nextPoint = createCountryRipple(nextCountryCode, nextColor);
|
||||||
|
|
||||||
if (currentPoint) {
|
if (currentPoint) {
|
||||||
ripplePoints.push({
|
ripplePoints.push(currentPoint);
|
||||||
...currentPoint,
|
|
||||||
color: lineColor, // 添加颜色信息
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nextPoint) {
|
if (nextPoint) {
|
||||||
ripplePoints.push({
|
ripplePoints.push(nextPoint);
|
||||||
...nextPoint,
|
|
||||||
color: nextItem.color || lineColor, // 添加颜色信息
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查是否应该绘制连线
|
// 检查是否应该绘制连线
|
||||||
if (currentItem.isLine !== false) {
|
if (currentItem.isLine !== false) {
|
||||||
const lineItem = getLineItem(countryCode, nextCountryCode);
|
const lineItem = getLineItem(countryCode, nextCountryCode, lineColor);
|
||||||
// 添加颜色信息到连线数据
|
solidData[0][1].push(lineItem);
|
||||||
solidData[0][1].push({
|
|
||||||
...lineItem,
|
|
||||||
color: lineColor, // 添加颜色信息
|
|
||||||
} as any);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -397,14 +369,17 @@ export const WorldGeo = memo(
|
|||||||
const toCoord = geoCoordMap[dataIndex?.[1]?.country_code ?? ""];
|
const toCoord = geoCoordMap[dataIndex?.[1]?.country_code ?? ""];
|
||||||
const fromCountry = dataIndex?.[0]?.country_code ?? "";
|
const fromCountry = dataIndex?.[0]?.country_code ?? "";
|
||||||
const toCountry = dataIndex?.[1]?.country_code ?? "";
|
const toCountry = dataIndex?.[1]?.country_code ?? "";
|
||||||
const lineColor = (dataIndex as any)?.color || "#0ea5e9"; // 获取线条颜色
|
// 使用每条线自己的颜色
|
||||||
|
const lineColor = dataIndex?.[0]?.color || "#0ea5e9";
|
||||||
|
|
||||||
if (fromCoord && toCoord) {
|
if (fromCoord && toCoord) {
|
||||||
res.push({
|
res.push({
|
||||||
coords: [fromCoord, toCoord],
|
coords: [fromCoord, toCoord],
|
||||||
lineStyle: {
|
lineStyle: {
|
||||||
color: lineColor, // 使用自定义颜色
|
color: lineColor, // 使用自定义颜色
|
||||||
},
|
},
|
||||||
|
// 保存颜色信息用于飞行特效
|
||||||
|
color: lineColor
|
||||||
});
|
});
|
||||||
// 计算中点,考虑曲线的弧度
|
// 计算中点,考虑曲线的弧度
|
||||||
const curveness = -0.4; // 与飞线弧度相同
|
const curveness = -0.4; // 与飞线弧度相同
|
||||||
@ -433,9 +408,11 @@ export const WorldGeo = memo(
|
|||||||
// 创建双层点效果 - 大点
|
// 创建双层点效果 - 大点
|
||||||
const createDualLayerPoint = (
|
const createDualLayerPoint = (
|
||||||
lastExit: LinesItemType,
|
lastExit: LinesItemType,
|
||||||
isMainPath: boolean = true,
|
isMainPath: boolean = true
|
||||||
color?: string
|
|
||||||
) => {
|
) => {
|
||||||
|
// 使用点自己的颜色
|
||||||
|
const pointColor = lastExit.color || "#0ea5e9";
|
||||||
|
|
||||||
// 创建数据数组,用于两个散点图层
|
// 创建数据数组,用于两个散点图层
|
||||||
const pointData = lastExit
|
const pointData = lastExit
|
||||||
? [lastExit].map((v) => {
|
? [lastExit].map((v) => {
|
||||||
@ -444,6 +421,7 @@ export const WorldGeo = memo(
|
|||||||
value: v.value,
|
value: v.value,
|
||||||
datas: {
|
datas: {
|
||||||
country_code: v.country_code,
|
country_code: v.country_code,
|
||||||
|
color: pointColor // 保存颜色信息
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
@ -451,16 +429,18 @@ export const WorldGeo = memo(
|
|||||||
// 根据是否是主路径设置不同的大小和颜色
|
// 根据是否是主路径设置不同的大小和颜色
|
||||||
const outerSize = isMainPath ? 8 : 4;
|
const outerSize = isMainPath ? 8 : 4;
|
||||||
const innerSize = isMainPath ? 4 : 2;
|
const innerSize = isMainPath ? 4 : 2;
|
||||||
// 使用传入的颜色或默认颜色
|
|
||||||
const outerColor = color || "#0ea5e9";
|
|
||||||
const innerColor = "#FFFFFF"; // 白色内层
|
const innerColor = "#FFFFFF"; // 白色内层
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
// 外层蓝色点,带涟漪效果
|
// 外层彩色点,带涟漪效果
|
||||||
type: "effectScatter",
|
type: "effectScatter",
|
||||||
coordinateSystem: "geo",
|
coordinateSystem: "geo",
|
||||||
zlevel: 3,
|
zlevel: 3,
|
||||||
color: outerColor,
|
itemStyle: {
|
||||||
|
color: function(params: any) {
|
||||||
|
return params.data.datas.color;
|
||||||
|
}
|
||||||
|
},
|
||||||
symbol: "circle",
|
symbol: "circle",
|
||||||
symbolSize: outerSize,
|
symbolSize: outerSize,
|
||||||
rippleEffect: {
|
rippleEffect: {
|
||||||
@ -516,17 +496,17 @@ export const WorldGeo = memo(
|
|||||||
// 添加新方法:根据经纬度数组创建蓝色涟漪小点(不包含白色内层点)
|
// 添加新方法:根据经纬度数组创建蓝色涟漪小点(不包含白色内层点)
|
||||||
const createRipplePointsFromCoordinates = (
|
const createRipplePointsFromCoordinates = (
|
||||||
coordinates: [number, number][],
|
coordinates: [number, number][],
|
||||||
series: echarts.SeriesOption[]
|
series: echarts.SeriesOption[],
|
||||||
|
color: string = "#01FF5E"
|
||||||
) => {
|
) => {
|
||||||
if (!coordinates || coordinates.length === 0) return;
|
if (!coordinates || coordinates.length === 0) return;
|
||||||
// 使用selectedApp.color或默认蓝色
|
|
||||||
const outerColor = "#01FF5E";
|
|
||||||
// 只创建外层带涟漪效果的点
|
// 只创建外层带涟漪效果的点
|
||||||
series.push({
|
series.push({
|
||||||
type: "effectScatter",
|
type: "effectScatter",
|
||||||
coordinateSystem: "geo",
|
coordinateSystem: "geo",
|
||||||
zlevel: 3,
|
zlevel: 3,
|
||||||
color: outerColor,
|
color: color,
|
||||||
symbol: "circle",
|
symbol: "circle",
|
||||||
symbolSize: 6,
|
symbolSize: 6,
|
||||||
rippleEffect: {
|
rippleEffect: {
|
||||||
@ -551,13 +531,15 @@ export const WorldGeo = memo(
|
|||||||
) => {
|
) => {
|
||||||
// 创建数据数组
|
// 创建数据数组
|
||||||
const pointData = dataItems.map((dataItem: LinesDataType) => {
|
const pointData = dataItems.map((dataItem: LinesDataType) => {
|
||||||
const color = (dataItem as any).color || "#0ea5e9"; // 获取颜色信息
|
// 使用每个点自己的颜色
|
||||||
|
const pointColor = dataItem[0].color || "#0ea5e9";
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: dataItem[0].name,
|
name: dataItem[0].name,
|
||||||
value: geoCoordMap[dataItem[0].country_code],
|
value: geoCoordMap[dataItem[0].country_code],
|
||||||
datas: {
|
datas: {
|
||||||
country_code: dataItem[0].country_code,
|
country_code: dataItem[0].country_code,
|
||||||
color: color, // 保存颜色信息
|
color: pointColor // 保存颜色信息
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@ -574,7 +556,7 @@ export const WorldGeo = memo(
|
|||||||
// 使用回调函数根据数据项设置颜色
|
// 使用回调函数根据数据项设置颜色
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
color: function (params: any) {
|
color: function (params: any) {
|
||||||
return params.data.datas.color || "#0ea5e9";
|
return params.data.datas.color;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
symbol: "circle",
|
symbol: "circle",
|
||||||
@ -633,7 +615,7 @@ export const WorldGeo = memo(
|
|||||||
// 使用回调函数根据数据项设置颜色
|
// 使用回调函数根据数据项设置颜色
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
color: function (params: any) {
|
color: function (params: any) {
|
||||||
return params.data.datas.color || "#0ea5e9";
|
return params.data.datas.color;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
symbol: "circle",
|
symbol: "circle",
|
||||||
@ -675,7 +657,7 @@ export const WorldGeo = memo(
|
|||||||
value: point.value,
|
value: point.value,
|
||||||
datas: {
|
datas: {
|
||||||
country_code: point.country_code,
|
country_code: point.country_code,
|
||||||
color: point.color || "#0ea5e9", // 保存颜色信息
|
color: point.color || "#0ea5e9", // 使用点自己的颜色
|
||||||
},
|
},
|
||||||
})),
|
})),
|
||||||
} as echarts.SeriesOption;
|
} as echarts.SeriesOption;
|
||||||
@ -712,85 +694,95 @@ export const WorldGeo = memo(
|
|||||||
if (item[1].length === 0) {
|
if (item[1].length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const lastExit = item[1]?.[item[1].length - 1]?.[1] ?? null;
|
|
||||||
const lastExitColor =
|
// 处理每条线段
|
||||||
(item[1]?.[item[1].length - 1] as any)?.color || "#0ea5e9";
|
item[1].forEach((lineSegment, index) => {
|
||||||
|
const fromPoint = lineSegment[0];
|
||||||
// 添加飞行线
|
const toPoint = lineSegment[1];
|
||||||
series.push({
|
const lineColor = fromPoint.color || "#0ea5e9";
|
||||||
name: item[0],
|
|
||||||
type: "lines",
|
// 添加单条飞行线
|
||||||
zlevel: 1,
|
|
||||||
label: {
|
|
||||||
show: false, // 不使用内置标签
|
|
||||||
},
|
|
||||||
// 飞行线特效
|
|
||||||
effect: {
|
|
||||||
show: true, // 是否显示
|
|
||||||
period: 4, // 特效动画时间
|
|
||||||
trailLength: 0.7, // 特效尾迹长度。取从 0 到 1 的值,数值越大尾迹越长
|
|
||||||
color: lastExitColor, // 特效颜色
|
|
||||||
// symbol: planePathImg, // 特效图形标记
|
|
||||||
symbolSize: [10, 20],
|
|
||||||
},
|
|
||||||
// 线条样式
|
|
||||||
lineStyle: {
|
|
||||||
curveness: -0.4, // 飞线弧度
|
|
||||||
type: "solid", // 飞线类型
|
|
||||||
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, lastExitColor);
|
|
||||||
series.push(...exitNodes);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
otherLineList.forEach((line: any) => {
|
|
||||||
line.forEach((item: any) => {
|
|
||||||
const lastExit = item[1]?.[item[1].length - 1]?.[1] ?? null;
|
|
||||||
const lastExitColor =
|
|
||||||
(item[1]?.[item[1].length - 1] as any)?.color || "#0ea5e9";
|
|
||||||
// 添加虚线
|
|
||||||
series.push({
|
series.push({
|
||||||
name: item[0],
|
name: `${item[0]}-${index}`,
|
||||||
type: "lines",
|
type: "lines",
|
||||||
zlevel: 1,
|
zlevel: 1,
|
||||||
label: {
|
label: {
|
||||||
show: false,
|
show: false, // 不使用内置标签
|
||||||
|
},
|
||||||
|
// 飞行线特效
|
||||||
|
effect: {
|
||||||
|
show: true, // 是否显示
|
||||||
|
period: 4, // 特效动画时间
|
||||||
|
trailLength: 0.7, // 特效尾迹长度。取从 0 到 1 的值,数值越大尾迹越长
|
||||||
|
color: lineColor, // 使用线段自己的颜色
|
||||||
|
// symbol: planePathImg, // 特效图形标记
|
||||||
|
symbolSize: [10, 20],
|
||||||
},
|
},
|
||||||
// 线条样式
|
// 线条样式
|
||||||
lineStyle: {
|
lineStyle: {
|
||||||
curveness: -0.4, // 飞线弧度
|
curveness: -0.4, // 飞线弧度
|
||||||
type: [5, 5], // 飞线类型
|
type: "solid", // 飞线类型
|
||||||
color: "#F0FFA2", // 飞线颜色
|
width: 1.5, // 飞线宽度
|
||||||
width: 0.5, // 飞线宽度
|
opacity: 0.1,
|
||||||
opacity: 0.6,
|
color: lineColor, // 使用线段自己的颜色
|
||||||
},
|
},
|
||||||
data: convertData(item[1]) as echarts.LinesSeriesOption["data"],
|
data: convertData([[fromPoint, toPoint]]) as echarts.LinesSeriesOption["data"],
|
||||||
});
|
});
|
||||||
// 添加路径点的双层效果(次要路径)
|
|
||||||
const pathPoints = createPathPoints(item[1], false);
|
// 添加起点的双层效果
|
||||||
series.push(...pathPoints);
|
const startNodes = createDualLayerPoint(fromPoint, true);
|
||||||
// 添加出口节点的双层效果(次要路径)
|
series.push(...startNodes);
|
||||||
if (lastExit) {
|
|
||||||
const exitNodes = createDualLayerPoint(
|
// 如果是最后一个线段,添加终点的双层效果
|
||||||
lastExit,
|
if (index === item[1].length - 1) {
|
||||||
false,
|
const endNodes = createDualLayerPoint(toPoint, true);
|
||||||
lastExitColor
|
series.push(...endNodes);
|
||||||
);
|
|
||||||
series.push(...exitNodes);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
otherLineList.forEach((line: any) => {
|
||||||
|
line.forEach((item: any) => {
|
||||||
|
// 处理每条虚线段
|
||||||
|
item[1].forEach((lineSegment: any, index: number) => {
|
||||||
|
const fromPoint = lineSegment[0];
|
||||||
|
const toPoint = lineSegment[1];
|
||||||
|
const lineColor = fromPoint.color || "#F0FFA2";
|
||||||
|
|
||||||
|
// 添加虚线
|
||||||
|
series.push({
|
||||||
|
name: `${item[0]}-dashed-${index}`,
|
||||||
|
type: "lines",
|
||||||
|
zlevel: 1,
|
||||||
|
label: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
// 线条样式
|
||||||
|
lineStyle: {
|
||||||
|
curveness: -0.4, // 飞线弧度
|
||||||
|
type: [5, 5], // 飞线类型
|
||||||
|
color: lineColor, // 使用线段自己的颜色
|
||||||
|
width: 0.5, // 飞线宽度
|
||||||
|
opacity: 0.6,
|
||||||
|
},
|
||||||
|
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);
|
||||||
|
series.push(...endNodes);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 创建A点和B点,并添加飞线和标签
|
// 创建A点和B点,并添加飞线和标签
|
||||||
const createSpecialPoints = (series: echarts.SeriesOption[]) => {
|
const createSpecialPoints = (series: echarts.SeriesOption[]) => {
|
||||||
// 定义点A和点B的坐标
|
// 定义点A和点B的坐标
|
||||||
@ -940,9 +932,12 @@ export const WorldGeo = memo(
|
|||||||
// getMianLineTipData(series); // 添加主线tip 暂时隐藏
|
// getMianLineTipData(series); // 添加主线tip 暂时隐藏
|
||||||
createSpecialPoints(series); // 添加特殊点和飞线
|
createSpecialPoints(series); // 添加特殊点和飞线
|
||||||
if (dataInfo.passAuthentication?.authenticationPoint) {
|
if (dataInfo.passAuthentication?.authenticationPoint) {
|
||||||
|
// 使用认证点的颜色
|
||||||
|
const authColor = dataInfo.passAuthentication.color || "#01FF5E";
|
||||||
createRipplePointsFromCoordinates(
|
createRipplePointsFromCoordinates(
|
||||||
dataInfo.passAuthentication?.authenticationPoint || [],
|
dataInfo.passAuthentication?.authenticationPoint || [],
|
||||||
series
|
series,
|
||||||
|
authColor
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const option = {
|
const option = {
|
||||||
@ -1163,4 +1158,4 @@ export const WorldGeo = memo(
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
Loading…
x
Reference in New Issue
Block a user