diff --git a/src/pages/anti-forensics-forwarding/components/world-geo.tsx b/src/pages/anti-forensics-forwarding/components/world-geo.tsx index 3eb0745..b1bb58f 100644 --- a/src/pages/anti-forensics-forwarding/components/world-geo.tsx +++ b/src/pages/anti-forensics-forwarding/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=="; +// 连线动画的间隔时间(毫秒) +const LINE_ANIMATION_INTERVAL = 500; // 3秒 interface LinesItemType { name: string; country_code: string; @@ -30,23 +29,21 @@ const createCountryRipple = (countryCode: string, color?: string) => { }; export const WorldGeo = memo( ({ - currentValue, - newHomeProxies, - tooltipType, - tooltipClosed, + nestedEncryption, + passAuthentication, + dynamicRouteGeneration, setTooltipClosed, }: { - currentValue: any; - newHomeProxies: any; + nestedEncryption: any; + passAuthentication: any; + dynamicRouteGeneration: any; tooltipType: string; tooltipClosed: boolean; setTooltipClosed: (value: boolean) => void; }) => { - // const queryClient = useQueryClient() // 嵌套加密ref const customTooltipRef = useRef(null); // 流量混淆ref - const customTooltip2Ref = useRef(null); const proxyGeoRef = useRef(null); const preMainToData = useRef<{ country_code: string }[]>([]); const lineMidpointsRef = useRef< @@ -59,46 +56,405 @@ export const WorldGeo = memo( >([]); const labelContainerRef = useRef(null); const labelsRef = useRef([]); - const mainToData = useMemo(() => { - // 使用新的数据结构 - const proxiesList = currentValue ?? []; - // 初始化数据数组 - 不再包含 startCountry - const data: any = []; - // 遍历代理列表 - proxiesList.forEach((proxyItem: any) => { - // 检查是否有数据数组 - if (proxyItem.data && Array.isArray(proxyItem.data)) { - // 遍历数据数组中的每个项目 - proxyItem.data.forEach((item: any) => { - // 如果有 ingress_country_code,则添加一对起点和终点 - if (item.ingress_country_code) { - // 添加起点(country_code) - data.push({ - country_code: item.country_code, - type: "start", - isLine: proxyItem.isLine, // 保存连线标志 - color: proxyItem.color || item.color, // 添加颜色属性 - }); - // 添加终点(ingress_country_code) - data.push({ - country_code: item.ingress_country_code, - type: "end", - isLine: proxyItem.isLine, // 保存连线标志 - color: proxyItem.color || item.color, // 添加颜色属性 - }); + + // 添加状态来跟踪当前显示的连线索引 + 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 [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) => { + 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 { - // 如果没有 ingress_country_code,只添加 country_code - data.push({ - country_code: item.country_code, - isLine: proxyItem.isLine, // 保存连线标志 - color: proxyItem.color || item.color, // 添加颜色属性 - }); + // 更新已存在点的颜色 + const existingIndex = newPoints.findIndex(p => p.country_code === point.country_code); + if (existingIndex !== -1 && point.color) { + newPoints[existingIndex].color = point.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元素 const createCustomTooltip = () => { // 如果已经存在自定义提示框,则移除它 @@ -112,10 +468,6 @@ export const WorldGeo = memo( 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 - }
+
嵌套加密
- +
@@ -162,11 +504,12 @@ export const WorldGeo = memo( // 定位提示框 positionCustomTooltip(); }; + // 定位自定义提示框 - 优化版本 const positionCustomTooltip = () => { if (!customTooltipRef.current || !proxyGeoRef.current) return; // 找到US点 - const coords = geoCoordMap[currentValue?.[0]?.code ?? "GL"]; + const coords = geoCoordMap[nestedEncryption?.[0]?.code ?? "GL"]; if (!coords) return; try { // 将地理坐标转换为屏幕坐标 @@ -184,174 +527,7 @@ 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(); - }; - // 定位自定义提示框 - 优化版本 - const positionCustomTooltip2 = () => { - if (!customTooltip2Ref.current || !proxyGeoRef.current) return; - // 找到US点 - const coords = geoCoordMap[currentValue?.[0]?.code ?? "ZA"]; - if (!coords) return; - try { - // 将地理坐标转换为屏幕坐标 - const screenCoord = proxyGeoRef.current.convertToPixel("geo", coords); - if ( - screenCoord && - Array.isArray(screenCoord) && - screenCoord.length === 2 - ) { - // 设置提示框位置 - customTooltip2Ref.current.style.left = `${ - screenCoord[0] - 626 + 20 - }px`; - customTooltip2Ref.current.style.top = `${ - screenCoord[1] + 40 - 218 - }px`; - } - } catch (error) { - console.error("Error positioning tooltip:", error); - } - }; - const getLineItem = ( - preCode: string, - nextCode: string - ): [LinesItemType, LinesItemType] => { - return [ - { - name: countryCodeMap[preCode] ?? "", - value: geoCoordMap[preCode] ?? [], - country_code: preCode, - }, - { - name: countryCodeMap[nextCode] ?? "", - value: geoCoordMap[nextCode] ?? [], - country_code: nextCode, - }, - ]; - }; - const getLine = () => { - // 实现数据处理 - const solidData: LinesType[] = [["main", []]]; // 使用"main"替代startCountry.country_code - // 收集需要显示涟漪效果的所有点(包括连线和不连线的) - const ripplePoints: any[] = []; - - // 处理主路径数据 - for (let i = 0; i < mainToData.length; i++) { - // 如果是最后一个元素,则跳过(因为没有下一个元素作为终点) - if (i === mainToData.length - 1) continue; - const currentItem = mainToData[i]; - const nextItem = mainToData[i + 1]; - - // 获取当前国家代码和颜色 - const countryCode = currentItem.country_code.toUpperCase(); - const color = currentItem.color || "#0ea5e9"; // 获取颜色,如果没有则使用默认颜色 - - // 如果当前项是起点,下一项是终点 - if (currentItem.type === "start" && nextItem.type === "end") { - const startCode = countryCode; - const endCode = nextItem.country_code.toUpperCase(); - - // 无论是否连线,都添加点的涟漪效果 - const startPoint = createCountryRipple(startCode, color); - const endPoint = createCountryRipple( - endCode, - nextItem.color || color - ); - if (startPoint) ripplePoints.push(startPoint); - if (endPoint) ripplePoints.push(endPoint); - - // 检查是否应该绘制连线 - if (currentItem.isLine !== false) { - const lineItem = getLineItem(startCode, endCode); - // 添加颜色属性 - lineItem[0].color = color; - lineItem[1].color = nextItem.color || color; - solidData[0]?.[1].push(lineItem); - } - - // 跳过下一项,因为已经处理了 - i++; - } - // 常规情况:当前项到下一项 - else { - const nextCountryCode = nextItem.country_code.toUpperCase(); - - // 无论是否连线,都添加点的涟漪效果 - const currentPoint = createCountryRipple(countryCode, color); - const nextPoint = createCountryRipple( - nextCountryCode, - nextItem.color || color - ); - if (currentPoint) ripplePoints.push(currentPoint); - if (nextPoint) ripplePoints.push(nextPoint); - - // 检查是否应该绘制连线 - if (currentItem.isLine !== false) { - const lineItem = getLineItem(countryCode, nextCountryCode); - // 添加颜色属性 - lineItem[0].color = color; - lineItem[1].color = nextItem.color || color; - solidData[0]?.[1].push(lineItem); - } - } - } - - // 虚线数据处理(保持原有逻辑) - const otherLineList: any = []; - return { - solidData, - otherLineList, - ripplePoints, - }; - }; + // 获取连线经纬度数据 const convertData = (data: LinesDataType[]) => { const res = []; @@ -394,6 +570,7 @@ export const WorldGeo = memo( // lineMidpointsRef.current = midpoints; return res; }; + // 创建双层点效果 - 大点 const createDualLayerPoint = ( lastExit: LinesItemType, @@ -480,6 +657,7 @@ export const WorldGeo = memo( } as echarts.SeriesOption, ]; }; + // 添加新方法:根据经纬度数组创建蓝色涟漪小点(不包含白色内层点) const createRipplePointsFromCoordinates = ( coordinates: [number, number][], @@ -511,6 +689,7 @@ export const WorldGeo = memo( })), } as echarts.SeriesOption); }; + // 创建路径点的双层效果 const createPathPoints = ( dataItems: LinesDataType[], @@ -593,6 +772,7 @@ export const WorldGeo = memo( } as echarts.SeriesOption, ]; }; + // 创建带自定义提示框的涟漪点 const createRipplePointsWithTooltip = (ripplePoints: any) => { return { @@ -652,9 +832,11 @@ export const WorldGeo = memo( })), } as echarts.SeriesOption; }; + // 连线 series const getLianData = (series: echarts.SeriesOption[]) => { const { solidData, otherLineList, ripplePoints } = getLine(); + // 如果有需要显示涟漪效果的点,添加它们 if (ripplePoints.length > 0) { // 添加带自定义提示框的外层蓝色点 @@ -680,15 +862,17 @@ export const WorldGeo = memo( })), } as echarts.SeriesOption); } + + // 处理每个连线组 solidData.forEach((item) => { // 如果没有连线数据,则跳过 if (item[1].length === 0) { return; } - const lastExit = item[1]?.[item[1].length - 1]?.[1] ?? null; - // 获取当前路径的颜色 - const pathColor = item[1]?.[0]?.[0]?.color || "#0ea5e9"; // 从第一个点获取颜色,如果没有则使用默认颜色 - + + // 为每条连线创建飞行线 + const pathColor = item[0] === "nested" ? "#0ea5e9" : "#F0FFA2"; // 根据类型设置默认颜色 + // 添加飞行线 series.push({ name: item[0], @@ -703,7 +887,6 @@ export const WorldGeo = memo( period: 4, // 特效动画时间 trailLength: 0.7, // 特效尾迹长度。取从 0 到 1 的值,数值越大尾迹越长 color: pathColor, // 特效颜色 - // symbol: planePathImg, // 特效图形标记 symbolSize: [10, 20], }, // 线条样式 @@ -716,23 +899,27 @@ 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); - series.push(...exitNodes); - } + item[1].forEach(lineData => { + const lastExit = lineData[1]; + if (lastExit) { + const exitNodes = createDualLayerPoint(lastExit, true, lastExit.color || pathColor); + series.push(...exitNodes); + } + }); }); + + // 处理其他线(保持原有逻辑) otherLineList.forEach((line: any) => { line.forEach((item: any) => { const lastExit = item[1]?.[item[1].length - 1]?.[1] ?? null; // 获取当前路径的颜色 const pathColor = item[1]?.[0]?.[0]?.color || "#F0FFA2"; // 从第一个点获取颜色,如果没有则使用默认颜色 - // 添加虚线 series.push({ name: item[0], @@ -761,14 +948,15 @@ export const WorldGeo = memo( } }); }); + return true; }; - + // 创建A点和B点,并添加飞线和标签 const createSpecialPoints = (series: echarts.SeriesOption[]) => { // 定义点A和点B的坐标 - const pointA = geoCoordMap[currentValue[0]?.startPoint ?? "GL"]; - const pointB = geoCoordMap[currentValue[0]?.endPoint ?? "CA"]; + const pointA = geoCoordMap[passAuthentication[0]?.startPoint ?? "GL"]; + const pointB = geoCoordMap[passAuthentication[0]?.endPoint ?? "CA"]; const newPointB = [pointB[0] + 14, pointB[1] + 10]; // 添加A点 - 带涟漪效果的双层点 series.push( @@ -906,25 +1094,21 @@ export const WorldGeo = memo( }); return series; }; + const getOption = () => { const series: echarts.SeriesOption[] = []; getLianData(series); // getMianLineTipData(series); // 添加主线tip 暂时隐藏 if ( - tooltipType === "PASS_AUTHENTICATION" && - currentValue && - currentValue.length && - currentValue[0]?.authenticationPoint + passAuthentication.length && + passAuthentication[0]?.authenticationPoint ) { - console.log(currentValue, "values"); - createSpecialPoints(series); // 添加特殊点和飞线 createRipplePointsFromCoordinates( - currentValue[0]?.authenticationPoint || [], + passAuthentication[0]?.authenticationPoint || [], series ); } - const option = { backgroundColor: "transparent", // 全局提示框配置 @@ -1000,6 +1184,7 @@ export const WorldGeo = memo( }; return option; }; + // 创建DOM标签 const createDOMLabels = () => { // 清除现有标签 @@ -1065,6 +1250,7 @@ export const WorldGeo = memo( // 更新标签位置 updateLabelPositions(); }; + // 更新标签位置 const updateLabelPositions = () => { if (!proxyGeoRef.current || !labelContainerRef.current) return; @@ -1081,20 +1267,27 @@ export const WorldGeo = memo( } }); }; + const handleResize = () => { proxyGeoRef.current?.resize(); updateLabelPositions(); + positionCustomTooltip(); }; + + // 更新图表 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); + }, [nestedEncryptionLineIndex, dynamicRouteLineIndex, allPoints]); // 当连线索引或点变化时更新图表 + + useEffect(() => { + lineMidpointsRef.current = []; // 重置中点数据 const option = getOption(); proxyGeoRef.current?.setOption(option); // 创建DOM标签 setTimeout(createDOMLabels, 100); - }, [newHomeProxies, mainToData]); + }, [nestedEncryption, dynamicRouteGeneration, passAuthentication]); + useEffect(() => { const chartDom = document.getElementById("screenGeo"); proxyGeoRef.current = echarts.init(chartDom); @@ -1102,6 +1295,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); // 添加地图交互事件监听器 @@ -1121,34 +1321,19 @@ export const WorldGeo = memo( proxyGeoRef.current = null; }; }, []); + useEffect(() => { - if (tooltipType !== "PASS_AUTHENTICATION") { - lineMidpointsRef.current = []; - } - if (tooltipClosed) { - if (tooltipType === "NESTED_ENCRYPTION") { - createCustomTooltip(); - } - if (tooltipType === "TRAFFIC_OBFUSCATION") { - createCustomTooltip2(); - } - } else { - customTooltipRef.current?.remove(); - customTooltip2Ref.current?.remove(); - customTooltipRef.current = null; - customTooltip2Ref.current = null; - } + createCustomTooltip(); return () => { customTooltipRef.current?.remove(); - customTooltip2Ref.current?.remove(); customTooltipRef.current = null; - customTooltip2Ref.current = null; }; - }, [tooltipClosed, tooltipType, currentValue]); + }, []); + return (
); } -); +); \ No newline at end of file diff --git a/src/pages/anti-forensics-forwarding/data/mockData.ts b/src/pages/anti-forensics-forwarding/data/mockData.ts index f1c8dc2..46acccf 100644 --- a/src/pages/anti-forensics-forwarding/data/mockData.ts +++ b/src/pages/anti-forensics-forwarding/data/mockData.ts @@ -21,7 +21,7 @@ export const TRAFFIC_OBFUSCATION = { ingress_country_code: "cn", }, ], - isLine: true, + isLine: false, }; export const NESTED_ENCRYPTION = { type: "NESTED_ENCRYPTION", @@ -45,34 +45,34 @@ export const NESTED_ENCRYPTION = { ingress_country_code: "cn", }, ], - isLine: true, + isLine: false, }; export const DYNAMIC_ROUTE_GENERATOR = [ - { - type: "DYNAMIC_ROUTE_GENERATOR", - name: "动态路由生成", - data: [ - { - country_code: "us", - ingress_country_code: "ca", - }, - { - country_code: "ca", - ingress_country_code: "gl", - }, - { - country_code: "gl", - ingress_country_code: "by", - }, - { - country_code: "dz", - ingress_country_code: "cn", - }, - ], - color: "#48D3D5", - isLine: true, - }, + // { + // type: "DYNAMIC_ROUTE_GENERATOR", + // name: "动态路由生成", + // data: [ + // { + // country_code: "us", + // ingress_country_code: "ca", + // }, + // { + // country_code: "ca", + // ingress_country_code: "gl", + // }, + // { + // country_code: "gl", + // ingress_country_code: "by", + // }, + // { + // country_code: "dz", + // ingress_country_code: "cn", + // }, + // ], + // color: "#48D3D5", + // isLine: true, + // }, { type: "DYNAMIC_ROUTE_GENERATOR", name: "动态路由生成2", @@ -95,7 +95,7 @@ export const DYNAMIC_ROUTE_GENERATOR = [ }, ], 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", name: "通信认证", startPoint: "GL", @@ -424,26 +424,7 @@ export const PASS_AUTHENTICATION = { [-102.346771, 68.130366], ], data: [ - { - country_code: "gl", - ingress_country_code: "dz", - }, - { - country_code: "br", - ingress_country_code: "dz", - }, - { - country_code: "dz", - ingress_country_code: "ru", - }, - { - country_code: "dz", - ingress_country_code: "cn", - }, - { - country_code: "ru", - ingress_country_code: "za", - }, + ], isLine: true, -} +} \ 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 7db2436..ef7d510 100644 --- a/src/pages/anti-forensics-forwarding/index.tsx +++ b/src/pages/anti-forensics-forwarding/index.tsx @@ -1,6 +1,4 @@ import { useState } from "react"; -import { useSelector } from "react-redux"; -import { WorldGeo } from "./components/world-geo"; import NetflixSvg from "@/assets/svg/anti-forensics-forwarding/Netflix.svg?react"; import NetflixActiveSvg from "@/assets/svg/anti-forensics-forwarding/NetflixActive.svg?react"; @@ -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 FacebookSvg from "@/assets/svg/anti-forensics-forwarding/Facebook.svg?react"; import FacebookActiveSvg from "@/assets/svg/anti-forensics-forwarding/FacebookActive.svg?react"; -import { RootState } from "@/store"; import { - TRAFFIC_OBFUSCATION, NESTED_ENCRYPTION, DYNAMIC_ROUTE_GENERATOR, - APP_DIVERSION, PASS_AUTHENTICATION, } from "./data/mockData"; import "./index.scss"; import { - getApplicationDiversion, getDynamicRouteGeneration, getNestedEncryption, getPassAuthentication, - getTrafficObfuscation, } from "@/api/flying-line"; +import { WorldGeo } from "./components/world-geo"; export const DIALOGTYPE = { ADDNode: { @@ -167,92 +161,67 @@ export const CONST_TOOLTIP_TYPE = { }; const DecentralizedElasticNetwork = () => { - const { newHomeProxies } = useSelector( - (state: RootState) => state.web3Reducer - ); - const [tooltipType, setTooltipType] = useState( CONST_TOOLTIP_TYPE.PASS_AUTHENTICATION.type ); const [tooltipClosed, setTooltipClosed] = useState(false); - const [selectedApp, setSelectedApp] = useState(null); - const [dataInfo, setDataInfo] = useState(null); + const [dataInfo, setDataInfo] = useState({ + passAuthentication: [ + { + ...PASS_AUTHENTICATION, + }, + ], + nestedEncryption: [NESTED_ENCRYPTION], + dynamicRouteGeneration: DYNAMIC_ROUTE_GENERATOR, + }); + 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 currentValue = useMemo(() => { - let value = dataInfo; - - switch (tooltipType) { - case CONST_TOOLTIP_TYPE.APP_DIVERSION.type: - value = selectedApp ? [selectedApp] : []; - break; - default: - break; - } - return value; - }, [tooltipType, selectedApp,dataInfo]); - - const handleClickApp = (item: any) => { - setSelectedApp(item); - }; - - const getDataInfo = async () => { - let value = []; - - switch (tooltipType) { - case CONST_TOOLTIP_TYPE.NESTED_ENCRYPTION.type: - const nestedEncryption = await getNestedEncryption(); - value = [nestedEncryption.data]; - break; - case CONST_TOOLTIP_TYPE.TRAFFIC_OBFUSCATION.type: - const trafficObfuscation = await getTrafficObfuscation(); - value = [trafficObfuscation.data]; - break; - case CONST_TOOLTIP_TYPE.DYNAMIC_ROUTE_GENERATOR.type: - const dynamicRouteGeneration = await getDynamicRouteGeneration(); - value = dynamicRouteGeneration.data; - break; - // case CONST_TOOLTIP_TYPE.APP_DIVERSION.type: - // const applicationDiversion = await getApplicationDiversion(); - - // value = selectedApp ? [selectedApp] : []; - // break; - case CONST_TOOLTIP_TYPE.PASS_AUTHENTICATION.type: - const passAuthentication = await getPassAuthentication(); - value = [passAuthentication.data]; - - break; - default: - break; - } - console.log(value,'valuevalue') - setDataInfo(value); - }; - - const [appData, setAppData] = useState([]); - const appDiversion = useMemo(() => { - return Apps.map((item) => { - const findApp = appData.find( - (appItem:any) => item.name === appItem.name - ); - return { - ...item, - ...findApp, - }; - }); - }, [appData]); const initData = async () => { - const applicationDiversion = await getApplicationDiversion(); - - setAppData(applicationDiversion.data); + try { + const passAuthentication = await getPassAuthentication(); + 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(() => { - getDataInfo(); - },[tooltipType]) + timer = setInterval(() => { + getNewDynamicRouteGeneration(); + }, 5000); + return () => { + clearInterval(timer); + }; + }, [dynamicRouteGeneration]); useEffect(() => { initData(); + () => { setTooltipClosed(false); }; @@ -260,28 +229,11 @@ const DecentralizedElasticNetwork = () => { return (
-
- {tooltipType === CONST_TOOLTIP_TYPE.APP_DIVERSION.type && - appDiversion.map((item) => { - return ( -
handleClickApp(item)} - > - {selectedApp?.name === item?.name ? ( - - ) : ( - - )} -
- ); - })} -
{ onClick={() => { setTooltipType(CONST_TOOLTIP_TYPE.NESTED_ENCRYPTION.type); setTooltipClosed(true); + nestedEncryption[0].isLine = true; + setNestedEncryption([...nestedEncryption]); }} > 嵌套加密 @@ -310,6 +264,9 @@ const DecentralizedElasticNetwork = () => { className="bt1 cursor-pointer" onClick={() => { setTooltipType(CONST_TOOLTIP_TYPE.DYNAMIC_ROUTE_GENERATOR.type); + dynamicRouteGeneration[0].isLine = true; + istrue.current = true; + setDynamicRouteGeneration([...dynamicRouteGeneration]); }} > 动态路由生成 diff --git a/src/pages/decentralized-lastic-network/components/world-geo.tsx b/src/pages/decentralized-lastic-network/components/world-geo.tsx index ae1cc81..5b2cd89 100644 --- a/src/pages/decentralized-lastic-network/components/world-geo.tsx +++ b/src/pages/decentralized-lastic-network/components/world-geo.tsx @@ -21,7 +21,7 @@ type LinesDataType = [LinesItemType, LinesItemType]; type LinesType = [string, LinesDataType[]]; // 连线动画的间隔时间(毫秒) -const LINE_ANIMATION_INTERVAL = 3000; // 3秒 +const LINE_ANIMATION_INTERVAL = 500; // 3秒 // 创建单个国家的涟漪效果 const createCountryRipple = (countryCode: string) => { @@ -73,7 +73,7 @@ export const WorldGeo = memo(({ screenData }: { screenData: any }) => { // 启动连线动画 setTimeout(() => { startLineAnimation(lineConnections); - }, 500); + }, 0); } // 如果 isLine 从 true 变为 false,重置连线索引 diff --git a/src/pages/new-home/components/world-geo.tsx b/src/pages/new-home/components/world-geo.tsx index 0b9a295..35fdf58 100644 --- a/src/pages/new-home/components/world-geo.tsx +++ b/src/pages/new-home/components/world-geo.tsx @@ -13,17 +13,19 @@ interface LinesItemType { name: string; country_code: string; value: number[]; + color?: string; // 添加颜色属性 } type LinesDataType = [LinesItemType, LinesItemType]; type LinesType = [string, LinesDataType[]]; // 创建单个国家的涟漪效果 -const createCountryRipple = (countryCode: string) => { +const createCountryRipple = (countryCode: string, color?: string) => { const coords = geoCoordMap[countryCode]; if (!coords) return null; return { name: countryCodeMap[countryCode] ?? "", value: coords, country_code: countryCode, + color: color, // 添加颜色属性 }; }; export const WorldGeo = memo( @@ -264,21 +266,23 @@ export const WorldGeo = memo( console.error("Error positioning tooltip:", error); } }; - const getLineItem = ( preCode: string, - nextCode: 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, // 添加颜色属性 }, ]; }; @@ -289,7 +293,6 @@ export const WorldGeo = memo( const ripplePoints: any[] = []; // 收集每个点的颜色信息 const pointColors: Record = {}; - // 处理主路径数据 for (let i = 0; i < mainToData.length; i++) { // 如果是最后一个元素,则跳过(因为没有下一个元素作为终点) @@ -300,44 +303,27 @@ export const WorldGeo = memo( const countryCode = currentItem.country_code.toUpperCase(); // 获取颜色信息 const lineColor = currentItem.color || "#0ea5e9"; // 默认颜色 - // 保存点的颜色信息 pointColors[countryCode] = lineColor; - // 如果当前项是起点,下一项是终点 if (currentItem.type === "start" && nextItem.type === "end") { const startCode = countryCode; const endCode = nextItem.country_code.toUpperCase(); - // 保存终点的颜色信息 pointColors[endCode] = lineColor; - // 无论是否连线,都添加点的涟漪效果 - const startPoint = createCountryRipple(startCode); - const endPoint = createCountryRipple(endCode); - + const startPoint = createCountryRipple(startCode, lineColor); + const endPoint = createCountryRipple(endCode, lineColor); if (startPoint) { - ripplePoints.push({ - ...startPoint, - color: lineColor, // 添加颜色信息 - }); + ripplePoints.push(startPoint); } - if (endPoint) { - ripplePoints.push({ - ...endPoint, - color: lineColor, // 添加颜色信息 - }); + ripplePoints.push(endPoint); } - // 检查是否应该绘制连线 if (currentItem.isLine !== false) { - const lineItem = getLineItem(startCode, endCode); - // 添加颜色信息到连线数据 - solidData[0][1].push({ - ...lineItem, - color: lineColor, // 添加颜色信息 - } as any); + const lineItem = getLineItem(startCode, endCode, lineColor); + solidData[0][1].push(lineItem); } // 跳过下一项,因为已经处理了 i++; @@ -345,36 +331,22 @@ export const WorldGeo = memo( // 常规情况:当前项到下一项 else { const nextCountryCode = nextItem.country_code.toUpperCase(); - + const nextColor = nextItem.color || "#0ea5e9"; // 获取下一个点的颜色 // 保存下一个点的颜色信息 - pointColors[nextCountryCode] = nextItem.color || lineColor; - + pointColors[nextCountryCode] = nextColor; // 无论是否连线,都添加点的涟漪效果 - const currentPoint = createCountryRipple(countryCode); - const nextPoint = createCountryRipple(nextCountryCode); - + const currentPoint = createCountryRipple(countryCode, lineColor); + const nextPoint = createCountryRipple(nextCountryCode, nextColor); if (currentPoint) { - ripplePoints.push({ - ...currentPoint, - color: lineColor, // 添加颜色信息 - }); + ripplePoints.push(currentPoint); } - if (nextPoint) { - ripplePoints.push({ - ...nextPoint, - color: nextItem.color || lineColor, // 添加颜色信息 - }); + ripplePoints.push(nextPoint); } - // 检查是否应该绘制连线 if (currentItem.isLine !== false) { - const lineItem = getLineItem(countryCode, nextCountryCode); - // 添加颜色信息到连线数据 - solidData[0][1].push({ - ...lineItem, - color: lineColor, // 添加颜色信息 - } as any); + const lineItem = getLineItem(countryCode, nextCountryCode, lineColor); + solidData[0][1].push(lineItem); } } } @@ -397,14 +369,17 @@ export const WorldGeo = memo( const toCoord = geoCoordMap[dataIndex?.[1]?.country_code ?? ""]; const fromCountry = dataIndex?.[0]?.country_code ?? ""; const toCountry = dataIndex?.[1]?.country_code ?? ""; - const lineColor = (dataIndex as any)?.color || "#0ea5e9"; // 获取线条颜色 - + // 使用每条线自己的颜色 + const lineColor = dataIndex?.[0]?.color || "#0ea5e9"; + if (fromCoord && toCoord) { res.push({ coords: [fromCoord, toCoord], lineStyle: { color: lineColor, // 使用自定义颜色 }, + // 保存颜色信息用于飞行特效 + color: lineColor }); // 计算中点,考虑曲线的弧度 const curveness = -0.4; // 与飞线弧度相同 @@ -433,9 +408,11 @@ export const WorldGeo = memo( // 创建双层点效果 - 大点 const createDualLayerPoint = ( lastExit: LinesItemType, - isMainPath: boolean = true, - color?: string + isMainPath: boolean = true ) => { + // 使用点自己的颜色 + const pointColor = lastExit.color || "#0ea5e9"; + // 创建数据数组,用于两个散点图层 const pointData = lastExit ? [lastExit].map((v) => { @@ -444,6 +421,7 @@ export const WorldGeo = memo( value: v.value, datas: { country_code: v.country_code, + color: pointColor // 保存颜色信息 }, }; }) @@ -451,16 +429,18 @@ export const WorldGeo = memo( // 根据是否是主路径设置不同的大小和颜色 const outerSize = isMainPath ? 8 : 4; const innerSize = isMainPath ? 4 : 2; - // 使用传入的颜色或默认颜色 - const outerColor = color || "#0ea5e9"; const innerColor = "#FFFFFF"; // 白色内层 return [ { - // 外层蓝色点,带涟漪效果 + // 外层彩色点,带涟漪效果 type: "effectScatter", coordinateSystem: "geo", zlevel: 3, - color: outerColor, + itemStyle: { + color: function(params: any) { + return params.data.datas.color; + } + }, symbol: "circle", symbolSize: outerSize, rippleEffect: { @@ -516,17 +496,17 @@ export const WorldGeo = memo( // 添加新方法:根据经纬度数组创建蓝色涟漪小点(不包含白色内层点) const createRipplePointsFromCoordinates = ( coordinates: [number, number][], - series: echarts.SeriesOption[] + series: echarts.SeriesOption[], + color: string = "#01FF5E" ) => { if (!coordinates || coordinates.length === 0) return; - // 使用selectedApp.color或默认蓝色 - const outerColor = "#01FF5E"; + // 只创建外层带涟漪效果的点 series.push({ type: "effectScatter", coordinateSystem: "geo", zlevel: 3, - color: outerColor, + color: color, symbol: "circle", symbolSize: 6, rippleEffect: { @@ -551,13 +531,15 @@ export const WorldGeo = memo( ) => { // 创建数据数组 const pointData = dataItems.map((dataItem: LinesDataType) => { - const color = (dataItem as any).color || "#0ea5e9"; // 获取颜色信息 + // 使用每个点自己的颜色 + 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: color, // 保存颜色信息 + color: pointColor // 保存颜色信息 }, }; }); @@ -574,7 +556,7 @@ export const WorldGeo = memo( // 使用回调函数根据数据项设置颜色 itemStyle: { color: function (params: any) { - return params.data.datas.color || "#0ea5e9"; + return params.data.datas.color; }, }, symbol: "circle", @@ -633,7 +615,7 @@ export const WorldGeo = memo( // 使用回调函数根据数据项设置颜色 itemStyle: { color: function (params: any) { - return params.data.datas.color || "#0ea5e9"; + return params.data.datas.color; }, }, symbol: "circle", @@ -675,7 +657,7 @@ export const WorldGeo = memo( value: point.value, datas: { country_code: point.country_code, - color: point.color || "#0ea5e9", // 保存颜色信息 + color: point.color || "#0ea5e9", // 使用点自己的颜色 }, })), } as echarts.SeriesOption; @@ -712,85 +694,95 @@ export const WorldGeo = memo( if (item[1].length === 0) { return; } - const lastExit = item[1]?.[item[1].length - 1]?.[1] ?? null; - const lastExitColor = - (item[1]?.[item[1].length - 1] as any)?.color || "#0ea5e9"; - - // 添加飞行线 - series.push({ - name: item[0], - 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"; - // 添加虚线 + + // 处理每条线段 + item[1].forEach((lineSegment, index) => { + const fromPoint = lineSegment[0]; + const toPoint = lineSegment[1]; + const lineColor = fromPoint.color || "#0ea5e9"; + + // 添加单条飞行线 series.push({ - name: item[0], + name: `${item[0]}-${index}`, type: "lines", zlevel: 1, label: { - show: false, + show: false, // 不使用内置标签 + }, + // 飞行线特效 + effect: { + show: true, // 是否显示 + period: 4, // 特效动画时间 + trailLength: 0.7, // 特效尾迹长度。取从 0 到 1 的值,数值越大尾迹越长 + color: lineColor, // 使用线段自己的颜色 + // symbol: planePathImg, // 特效图形标记 + symbolSize: [10, 20], }, // 线条样式 lineStyle: { curveness: -0.4, // 飞线弧度 - type: [5, 5], // 飞线类型 - color: "#F0FFA2", // 飞线颜色 - width: 0.5, // 飞线宽度 - opacity: 0.6, + type: "solid", // 飞线类型 + width: 1.5, // 飞线宽度 + opacity: 0.1, + 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); - // 添加出口节点的双层效果(次要路径) - if (lastExit) { - const exitNodes = createDualLayerPoint( - lastExit, - false, - lastExitColor - ); - series.push(...exitNodes); + + // 添加起点的双层效果 + const startNodes = createDualLayerPoint(fromPoint, true); + series.push(...startNodes); + + // 如果是最后一个线段,添加终点的双层效果 + if (index === item[1].length - 1) { + const endNodes = createDualLayerPoint(toPoint, true); + series.push(...endNodes); } }); }); + + 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; }; - // 创建A点和B点,并添加飞线和标签 const createSpecialPoints = (series: echarts.SeriesOption[]) => { // 定义点A和点B的坐标 @@ -940,9 +932,12 @@ export const WorldGeo = memo( // getMianLineTipData(series); // 添加主线tip 暂时隐藏 createSpecialPoints(series); // 添加特殊点和飞线 if (dataInfo.passAuthentication?.authenticationPoint) { + // 使用认证点的颜色 + const authColor = dataInfo.passAuthentication.color || "#01FF5E"; createRipplePointsFromCoordinates( dataInfo.passAuthentication?.authenticationPoint || [], - series + series, + authColor ); } const option = { @@ -1163,4 +1158,4 @@ export const WorldGeo = memo(
); } -); +); \ No newline at end of file