From da91340bcf3fdeee9e6776c5c22a0eec273ee4f1 Mon Sep 17 00:00:00 2001 From: liyuanhu Date: Wed, 16 Apr 2025 14:31:14 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E6=96=B0=E5=A2=9EmockData=EF=BC=8C?= =?UTF-8?q?=E4=BB=A5=E5=8F=8A=E4=B8=80=E4=BA=9B=E9=80=BB=E8=BE=91=E7=9A=84?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../anti-forensics-forwarding/LineLeft.svg | 3 + .../components/world-geo.tsx | 2239 ++++++++-------- .../data/mockData.ts | 447 ++++ .../anti-forensics-forwarding/index.scss | 16 + src/pages/anti-forensics-forwarding/index.tsx | 715 ++---- src/pages/new-home/components/world-geo.tsx | 2247 +++++++++-------- src/pages/new-home/index.tsx | 31 +- src/store/web3Slice.ts | 487 ++-- 8 files changed, 3336 insertions(+), 2849 deletions(-) create mode 100644 src/assets/svg/anti-forensics-forwarding/LineLeft.svg create mode 100644 src/pages/anti-forensics-forwarding/data/mockData.ts diff --git a/src/assets/svg/anti-forensics-forwarding/LineLeft.svg b/src/assets/svg/anti-forensics-forwarding/LineLeft.svg new file mode 100644 index 0000000..93610a5 --- /dev/null +++ b/src/assets/svg/anti-forensics-forwarding/LineLeft.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/pages/anti-forensics-forwarding/components/world-geo.tsx b/src/pages/anti-forensics-forwarding/components/world-geo.tsx index dc6b001..cbd969a 100644 --- a/src/pages/anti-forensics-forwarding/components/world-geo.tsx +++ b/src/pages/anti-forensics-forwarding/components/world-geo.tsx @@ -6,1130 +6,1255 @@ 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 ".."; +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=="; + "image://data:image/svg+xml;charset=utf-8;base64,PHN2ZyB3aWR0aD0iNjciIGhlaWdodD0iMTAyIiB2aWV3Qm94PSIwIDAgNjcgMTAyIiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8ZyBmaWx0ZXI9InVybCgjZmlsdGVyMF9mXzYxMTdfMjEyNDA3KSI+CjxwYXRoIGQ9Ik0zNC4yMTA5IDkxLjE4ODZMNTMuNjU3OCA0MC45NThDNTQuOTM4IDM3LjY1MTMgNTUuNzk4MyAzNC4xNTkyIDU1LjM1NjMgMzAuNjQxQzU0LjQzNTcgMjMuMzEyOCA1MC40Njg0IDExLjAyMDggMzQuMjExMiAxMS4wMjA4QzE5LjE5MDMgMTEuMDIwOCAxMy45MTEgMjEuNTE0NiAxMi4wNTU0IDI4Ljg5MTJDMTAuOTAxIDMzLjQ4MDYgMTEuOTkyNiAzOC4yMTg2IDEzLjgyMzEgNDIuNTgyN0wzNC4yMTA5IDkxLjE4ODZaIiBmaWxsPSJ1cmwoI3BhaW50MF9saW5lYXJfNjExN18yMTI0MDcpIi8+CjwvZz4KPGRlZnM+CjxmaWx0ZXIgaWQ9ImZpbHRlcjBfZl82MTE3XzIxMjQwNyIgeD0iMC44OTE3NDQiIHk9IjAuMzMxOTkiIHdpZHRoPSI2NS4yNzA3IiBoZWlnaHQ9IjEwMS41NDUiIGZpbHRlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIj4KPGZlRmxvb2QgZmxvb2Qtb3BhY2l0eT0iMCIgcmVzdWx0PSJCYWNrZ3JvdW5kSW1hZ2VGaXgiLz4KPGZlQmxlbmQgbW9kZT0ibm9ybWFsIiBpbj0iU291cmNlR3JhcGhpYyIgaW4yPSJCYWNrZ3JvdW5kSW1hZ2VGaXgiIHJlc3VsdD0ic2hhcGUiLz4KPGZlR2F1c3NpYW5CbHVyIHN0ZERldmlhdGlvbj0iNS4zNDQ0MSIgcmVzdWx0PSJlZmZlY3QxX2ZvcmVncm91bmRCbHVyXzYxMTdfMjEyNDA3Ii8+CjwvZmlsdGVyPgo8bGluZWFyR3JhZGllbnQgaWQ9InBhaW50MF9saW5lYXJfNjExN18yMTI0MDciIHgxPSIzNS4yODI2IiB5MT0iMTAuODU2NCIgeDI9IjM1LjI4MjYiIHkyPSI4Ni44NTY0IiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CjxzdG9wIHN0b3AtY29sb3I9IiMwMEYyRkYiLz4KPHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjMTUwMEZGIi8+CjwvbGluZWFyR3JhZGllbnQ+CjwvZGVmcz4KPC9zdmc+Cg=="; interface LinesItemType { - name: string; - country_code: string; - value: number[]; + name: string; + country_code: string; + value: number[]; + color?: string; // 添加颜色属性 } type LinesDataType = [LinesItemType, LinesItemType]; type LinesType = [string, LinesDataType[]]; // 创建单个国家的涟漪效果 -const createCountryRipple = (countryCode: string) => { - const coords = geoCoordMap[countryCode]; - if (!coords) return null; - return { - name: countryCodeMap[countryCode] ?? "", - value: coords, - country_code: countryCode, - }; +const createCountryRipple = (countryCode: string, color?: string) => { + const coords = geoCoordMap[countryCode]; + if (!coords) return null; + return { + name: countryCodeMap[countryCode] ?? "", + value: coords, + country_code: countryCode, + color: color || "#0ea5e9", // 添加颜色属性,如果没有则使用默认颜色 + }; }; export const WorldGeo = memo( - ({ - screenData, - selectedApp, - tooltipType, - tooltipClosed, - setTooltipClosed, - }: { - screenData: any; - selectedApp: any; - tooltipType: string; - tooltipClosed: boolean; - setTooltipClosed: (value: boolean) => void; - }) => { - // const queryClient = useQueryClient() - const customTooltipRef = useRef(null); - const proxyGeoRef = useRef(null); - const preMainToData = useRef<{ country_code: string }[]>([]); - const lineMidpointsRef = useRef< - { - id: string; - midpoint: number[]; - fromCountry: string; - toCountry: string; - }[] - >([]); - const labelContainerRef = useRef(null); - const labelsRef = useRef([]); - - const mainToData = useMemo(() => { - // 使用新的数据结构 - const proxiesList = - selectedApp && selectedApp?.jumpList - ? [selectedApp.jumpList] - : screenData?.proxy_info?.proxies ?? []; - // 初始化数据数组 - 不再包含 startCountry - const data: any = []; - // 遍历代理列表 - proxiesList.forEach((proxyItem: any) => { - // 检查是否有数据数组 - if (proxyItem.data && Array.isArray(proxyItem.data)) { - // 遍历数据数组中的每个项目 - proxyItem.data.forEach((item: any) => { - // 如果有 ingress_country_code,则添加一对起点和终点 - if (item.ingress_country_code) { - // 添加起点(country_code) - data.push({ - country_code: item.country_code, - type: "start", - isLine: proxyItem.isLine, // 保存连线标志 - }); - // 添加终点(ingress_country_code) - data.push({ - country_code: item.ingress_country_code, - type: "end", - isLine: proxyItem.isLine, // 保存连线标志 - }); - } else { - // 如果没有 ingress_country_code,只添加 country_code - data.push({ - country_code: item.country_code, - isLine: proxyItem.isLine, // 保存连线标志 - }); - } - }); - } - }); - return data; - }, [screenData, selectedApp]); - // 创建自定义提示框DOM元素 - const createCustomTooltip = () => { - // 如果已经存在自定义提示框,则移除它 - if (document.getElementById("custom-fixed-tooltip")) { - document.getElementById("custom-fixed-tooltip")?.remove(); + ({ + currentValue, + newHomeProxies, + selectedApp, + tooltipType, + tooltipClosed, + setTooltipClosed, + }: { + currentValue: any; + newHomeProxies: any; + selectedApp: any; + tooltipType: string; + tooltipClosed: boolean; + setTooltipClosed: (value: boolean) => void; + }) => { + // const queryClient = useQueryClient() + // 嵌套加密ref + const customTooltipRef = useRef(null); + // 流量混淆ref + const customTooltip2Ref = useRef(null); + const proxyGeoRef = useRef(null); + const preMainToData = useRef<{ country_code: string }[]>([]); + const lineMidpointsRef = useRef< + { + id: string; + midpoint: number[]; + fromCountry: string; + toCountry: string; + }[] + >([]); + const labelContainerRef = useRef(null); + const labelsRef = useRef([]); + const mainToData = useMemo(() => { + // 使用新的数据结构 + const proxiesList = currentValue ?? []; + // 初始化数据数组 - 不再包含 startCountry + const data: any = []; + // 遍历代理列表 + proxiesList.forEach((proxyItem: any) => { + // 检查是否有数据数组 + if (proxyItem.data && Array.isArray(proxyItem.data)) { + // 遍历数据数组中的每个项目 + proxyItem.data.forEach((item: any) => { + // 如果有 ingress_country_code,则添加一对起点和终点 + if (item.ingress_country_code) { + // 添加起点(country_code) + data.push({ + country_code: item.country_code, + type: "start", + isLine: proxyItem.isLine, // 保存连线标志 + color: proxyItem.color || item.color, // 添加颜色属性 + }); + // 添加终点(ingress_country_code) + data.push({ + country_code: item.ingress_country_code, + type: "end", + isLine: proxyItem.isLine, // 保存连线标志 + color: proxyItem.color || item.color, // 添加颜色属性 + }); + } else { + // 如果没有 ingress_country_code,只添加 country_code + data.push({ + country_code: item.country_code, + isLine: proxyItem.isLine, // 保存连线标志 + color: proxyItem.color || item.color, // 添加颜色属性 + }); } - // 创建自定义提示框 - const tooltip = document.createElement("div"); - tooltip.id = "custom-fixed-tooltip"; - tooltip.style.position = "fixed"; - tooltip.style.zIndex = "1000"; - tooltip.style.pointerEvents = "auto"; - tooltip.style.backgroundColor = "transparent"; - // 设置提示框内容 - const currentTooltipType = - CONST_TOOLTIP_TYPE[ - tooltipType as keyof typeof CONST_TOOLTIP_TYPE - ] || CONST_TOOLTIP_TYPE.NESTED_ENCRYPTION; - tooltip.innerHTML = ` + }); + } + }); + return data; + }, [currentValue]); + // 创建自定义提示框DOM元素 + const createCustomTooltip = () => { + // 如果已经存在自定义提示框,则移除它 + if (document.getElementById("custom-fixed-tooltip")) { + document.getElementById("custom-fixed-tooltip")?.remove(); + } + // 创建自定义提示框 + const tooltip = document.createElement("div"); + tooltip.id = "custom-fixed-tooltip"; + tooltip.style.position = "fixed"; + tooltip.style.zIndex = "1000"; + tooltip.style.pointerEvents = "auto"; + tooltip.style.backgroundColor = "transparent"; + // 设置提示框内容 + const currentTooltipType = + CONST_TOOLTIP_TYPE[tooltipType as keyof typeof CONST_TOOLTIP_TYPE] || + CONST_TOOLTIP_TYPE.NESTED_ENCRYPTION; + tooltip.innerHTML = `
${ - currentTooltipType.title + currentTooltipType.title }
+ CONST_TOOLTIP_TYPE.NESTED_ENCRYPTION.type === currentTooltipType.type + ? "image/nested-encryption.png" + : "image/traffic-obfuscation.png" + )}" alt="" />
`; - // 添加到DOM - document.body.appendChild(tooltip); - customTooltipRef.current = tooltip; - // 添加关闭按钮事件 - const closeButton = tooltip.querySelector(".close-icon"); - if (closeButton) { - closeButton.addEventListener("click", () => { - setTooltipClosed(false); - tooltip.remove(); - customTooltipRef.current = null; - }); - } - // 定位提示框 - positionCustomTooltip(); - }; - // 定位自定义提示框 - 优化版本 - const positionCustomTooltip = () => { - if (!customTooltipRef.current || !proxyGeoRef.current) return; - // 找到US点 - const coords = geoCoordMap["CA"]; - if (!coords) return; - try { - // 将地理坐标转换为屏幕坐标 - const screenCoord = proxyGeoRef.current.convertToPixel( - "geo", - coords - ); - if ( - screenCoord && - Array.isArray(screenCoord) && - screenCoord.length === 2 - ) { - // 设置提示框位置 - customTooltipRef.current.style.left = `${ - screenCoord[0] + 232 + 7 - }px`; - customTooltipRef.current.style.top = `${ - screenCoord[1] + 40 - 190 - }px`; - } - } catch (error) { - console.error("Error positioning tooltip:", error); - } - }; - // 主线每个节点tip竖线的经纬度,修改tip 竖线的高度也可以用这个 - const mianLineData = (data: typeof mainToData) => { - return ( - data - .map((item: any) => { - const countryCode = item.country_code.toUpperCase(); - - if ( - !( - ["RU", "FR"].includes(countryCode) && - item.type === "start" - ) - ) - return null; - const coords = geoCoordMap[countryCode] as - | [number, number] - | undefined; - if (!coords) return null; - return { - name: countryCodeMap[countryCode], - coords: [coords, [coords[0], coords[1] + 4]], - value: countryCode, - }; - }) - .filter((v: any) => !!v) ?? [] - ); - }; - const getLineItem = ( - preCode: string, - nextCode: string - ): [LinesItemType, LinesItemType] => { - return [ - { - name: countryCodeMap[preCode] ?? "", - value: geoCoordMap[preCode] ?? [], - country_code: preCode, - }, - { - name: countryCodeMap[nextCode] ?? "", - value: geoCoordMap[nextCode] ?? [], - country_code: nextCode, - }, - ]; - }; - const getLine = () => { - // 实现数据处理 - const solidData: LinesType[] = [["main", []]]; // 使用"main"替代startCountry.country_code - // 收集需要显示涟漪效果的所有点(包括连线和不连线的) - const ripplePoints: any[] = []; - // 处理主路径数据 - for (let i = 0; i < mainToData.length; i++) { - // 如果是最后一个元素,则跳过(因为没有下一个元素作为终点) - if (i === mainToData.length - 1) continue; - const currentItem = mainToData[i]; - const nextItem = mainToData[i + 1]; - // 获取当前国家代码 - const countryCode = currentItem.country_code.toUpperCase(); - // 如果当前项是起点,下一项是终点 - if (currentItem.type === "start" && nextItem.type === "end") { - const startCode = countryCode; - const endCode = nextItem.country_code.toUpperCase(); - // 无论是否连线,都添加点的涟漪效果 - const startPoint = createCountryRipple(startCode); - const endPoint = createCountryRipple(endCode); - if (startPoint) ripplePoints.push(startPoint); - if (endPoint) ripplePoints.push(endPoint); - // 检查是否应该绘制连线 - if (currentItem.isLine !== false) { - solidData[0]?.[1].push(getLineItem(startCode, endCode)); - } - // 跳过下一项,因为已经处理了 - i++; - } - // 常规情况:当前项到下一项 - else { - const nextCountryCode = nextItem.country_code.toUpperCase(); - // 无论是否连线,都添加点的涟漪效果 - const currentPoint = createCountryRipple(countryCode); - const nextPoint = createCountryRipple(nextCountryCode); - if (currentPoint) ripplePoints.push(currentPoint); - if (nextPoint) ripplePoints.push(nextPoint); - // 检查是否应该绘制连线 - if (currentItem.isLine !== false) { - solidData[0]?.[1].push( - getLineItem(countryCode, nextCountryCode) - ); - } - } - } - // 虚线数据处理(保持原有逻辑) - const pathList = - screenData?.path_list?.filter( - (v: any) => v.name !== screenData?.proxy_info?.name - ) ?? []; - const otherLineList = pathList.map(() => {}); + // 添加到DOM + document.body.appendChild(tooltip); + customTooltipRef.current = tooltip; + // 添加关闭按钮事件 + const closeButton = tooltip.querySelector(".close-icon"); + if (closeButton) { + closeButton.addEventListener("click", () => { + setTooltipClosed(false); + tooltip.remove(); + customTooltipRef.current = null; + }); + } + // 定位提示框 + positionCustomTooltip(); + }; + // 定位自定义提示框 - 优化版本 + const positionCustomTooltip = () => { + if (!customTooltipRef.current || !proxyGeoRef.current) return; + // 找到US点 + const coords = geoCoordMap["GL"]; + if (!coords) return; + try { + // 将地理坐标转换为屏幕坐标 + const screenCoord = proxyGeoRef.current.convertToPixel("geo", coords); + if ( + screenCoord && + Array.isArray(screenCoord) && + screenCoord.length === 2 + ) { + // 设置提示框位置 + customTooltipRef.current.style.left = `${screenCoord[0] + 232 + 7}px`; + customTooltipRef.current.style.top = `${screenCoord[1] + 40 - 190}px`; + } + } catch (error) { + console.error("Error positioning tooltip:", error); + } + }; + // 创建自定义提示框DOM元素 + const createCustomTooltip2 = () => { + // 如果已经存在自定义提示框,则移除它 + if (document.getElementById("custom-fixed-tooltip2")) { + document.getElementById("custom-fixed-tooltip2")?.remove(); + } + // 创建自定义提示框 + const tooltip = document.createElement("div"); + tooltip.id = "custom-fixed-tooltip2"; + tooltip.style.position = "fixed"; + tooltip.style.zIndex = "1000"; + tooltip.style.pointerEvents = "auto"; + tooltip.style.backgroundColor = "transparent"; + tooltip.innerHTML = ` +
+
+
+
+
流量混淆
+ +
+ + + +
+ +
+ `; + // 添加到DOM + document.body.appendChild(tooltip); + customTooltip2Ref.current = tooltip; + // 添加关闭按钮事件 + const closeButton = tooltip.querySelector(".close-icon"); + if (closeButton) { + closeButton.addEventListener("click", () => { + setTooltipClosed(false); + tooltip.remove(); + customTooltip2Ref.current = null; + }); + } + // 定位提示框 + positionCustomTooltip2(); + }; + // 定位自定义提示框 - 优化版本 + const positionCustomTooltip2 = () => { + if (!customTooltip2Ref.current || !proxyGeoRef.current) return; + // 找到US点 + const coords = geoCoordMap["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); + } + }; + // 主线每个节点tip竖线的经纬度,修改tip 竖线的高度也可以用这个 + const mianLineData = (data: typeof mainToData) => { + return ( + data + .map((item: any) => { + const countryCode = item.country_code.toUpperCase(); + if (!(["RU", "FR"].includes(countryCode) && item.type === "start")) + return null; + const coords = geoCoordMap[countryCode] as + | [number, number] + | undefined; + if (!coords) return null; return { - solidData, - otherLineList, - ripplePoints, + name: countryCodeMap[countryCode], + coords: [coords, [coords[0], coords[1] + 4]], + value: countryCode, }; - }; - // 获取连线经纬度数据 - const convertData = (data: LinesDataType[]) => { - const res = []; - const midpoints = []; + }) + .filter((v: any) => !!v) ?? [] + ); + }; + const getLineItem = ( + preCode: string, + nextCode: string + ): [LinesItemType, LinesItemType] => { + return [ + { + name: countryCodeMap[preCode] ?? "", + value: geoCoordMap[preCode] ?? [], + country_code: preCode, + }, + { + name: countryCodeMap[nextCode] ?? "", + value: geoCoordMap[nextCode] ?? [], + country_code: nextCode, + }, + ]; + }; + const getLine = () => { + // 实现数据处理 + const solidData: LinesType[] = [["main", []]]; // 使用"main"替代startCountry.country_code + // 收集需要显示涟漪效果的所有点(包括连线和不连线的) + const ripplePoints: any[] = []; - for (let index = 0; index < data.length; index++) { - const dataIndex = data[index]; - const fromCoord = - geoCoordMap[dataIndex?.[0]?.country_code ?? ""]; - const toCoord = geoCoordMap[dataIndex?.[1]?.country_code ?? ""]; - const fromCountry = dataIndex?.[0]?.country_code ?? ""; - const toCountry = dataIndex?.[1]?.country_code ?? ""; + // 处理主路径数据 + for (let i = 0; i < mainToData.length; i++) { + // 如果是最后一个元素,则跳过(因为没有下一个元素作为终点) + if (i === mainToData.length - 1) continue; + const currentItem = mainToData[i]; + const nextItem = mainToData[i + 1]; - if (fromCoord && toCoord) { - res.push([fromCoord, toCoord]); + // 获取当前国家代码和颜色 + const countryCode = currentItem.country_code.toUpperCase(); + const color = currentItem.color || "#0ea5e9"; // 获取颜色,如果没有则使用默认颜色 - // 计算中点,考虑曲线的弧度 - const curveness = -0.4; // 与飞线弧度相同 - const x1 = fromCoord[0]; - const y1 = fromCoord[1]; - const x2 = toCoord[0]; - const y2 = toCoord[1]; + // 如果当前项是起点,下一项是终点 + if (currentItem.type === "start" && nextItem.type === "end") { + const startCode = countryCode; + const endCode = nextItem.country_code.toUpperCase(); - // 计算控制点 - const cpx = (x1 + x2) / 2 - (y2 - y1) * curveness; - const cpy = (y1 + y2) / 2 - (x1 - x2) * curveness; + // 无论是否连线,都添加点的涟漪效果 + const startPoint = createCountryRipple(startCode, color); + const endPoint = createCountryRipple( + endCode, + nextItem.color || color + ); + if (startPoint) ripplePoints.push(startPoint); + if (endPoint) ripplePoints.push(endPoint); - // 计算曲线上的中点 (t=0.5 时的贝塞尔曲线点) - const midX = x1 * 0.25 + cpx * 0.5 + x2 * 0.25; - const midY = y1 * 0.25 + cpy * 0.5 + y2 * 0.25; + // 检查是否应该绘制连线 + if (currentItem.isLine !== false) { + const lineItem = getLineItem(startCode, endCode); + // 添加颜色属性 + lineItem[0].color = color; + lineItem[1].color = nextItem.color || color; + solidData[0]?.[1].push(lineItem); + } - midpoints.push({ - id: `line-label-${index}`, - midpoint: [midX, midY], - fromCountry, - toCountry, - }); - } - } + // 跳过下一项,因为已经处理了 + i++; + } + // 常规情况:当前项到下一项 + else { + const nextCountryCode = nextItem.country_code.toUpperCase(); - // 更新中点引用 - lineMidpointsRef.current = midpoints; + // 无论是否连线,都添加点的涟漪效果 + const currentPoint = createCountryRipple(countryCode, color); + const nextPoint = createCountryRipple( + nextCountryCode, + nextItem.color || color + ); + if (currentPoint) ripplePoints.push(currentPoint); + if (nextPoint) ripplePoints.push(nextPoint); - return res; - }; - // 创建双层点效果 - 大点 - const createDualLayerPoint = ( - lastExit: LinesItemType, - isMainPath: boolean = true - ) => { - // 创建数据数组,用于两个散点图层 - const pointData = lastExit - ? [lastExit].map((v) => { - return { - name: v.name, - value: v.value, - datas: { - country_code: v.country_code, - }, - }; - }) - : []; - // 根据是否是主路径设置不同的大小和颜色 - const outerSize = isMainPath ? 8 : 4; - const innerSize = isMainPath ? 4 : 2; - // Use selectedApp.color if available, otherwise default to blue - const outerColor = selectedApp?.color || "#0ea5e9"; // Use selectedApp.color with fallback - const innerColor = "#FFFFFF"; // 白色内层 - return [ - { - // 外层蓝色点,带涟漪效果 - type: "effectScatter", - coordinateSystem: "geo", - zlevel: 3, - color: outerColor, - symbol: "circle", - symbolSize: outerSize, - rippleEffect: { - period: 8, // 动画时间,值越小速度越快 - brushType: "stroke", // 波纹绘制方式 stroke - scale: 6, // 波纹圆环最大限制,值越大波纹越大 - brushWidth: 2, - }, - label: { - show: false, - }, - tooltip: { - show: false, - trigger: "item", - showContent: true, - alwaysShowContent: true, - formatter: (params: any) => { - // const countryCode = params.data.datas.country_code; - // const countryName = params.data.name; - // 创建自定义HTML提示框 - return ` + // 检查是否应该绘制连线 + if (currentItem.isLine !== false) { + const lineItem = getLineItem(countryCode, nextCountryCode); + // 添加颜色属性 + lineItem[0].color = color; + lineItem[1].color = nextItem.color || color; + solidData[0]?.[1].push(lineItem); + } + } + } + + // 虚线数据处理(保持原有逻辑) + const otherLineList: any = []; + return { + solidData, + otherLineList, + ripplePoints, + }; + }; + // 获取连线经纬度数据 + const convertData = (data: LinesDataType[]) => { + const res = []; + const midpoints = []; + for (let index = 0; index < data.length; index++) { + const dataIndex = data[index]; + const fromCoord = geoCoordMap[dataIndex?.[0]?.country_code ?? ""]; + const toCoord = geoCoordMap[dataIndex?.[1]?.country_code ?? ""]; + const fromCountry = dataIndex?.[0]?.country_code ?? ""; + const toCountry = dataIndex?.[1]?.country_code ?? ""; + if (fromCoord && toCoord) { + res.push({ + coords: [fromCoord, toCoord], + // 添加颜色属性 + lineStyle: { + color: dataIndex?.[0]?.color || "#0ea5e9", + }, + }); + // 计算中点,考虑曲线的弧度 + const curveness = -0.4; // 与飞线弧度相同 + const x1 = fromCoord[0]; + const y1 = fromCoord[1]; + const x2 = toCoord[0]; + const y2 = toCoord[1]; + // 计算控制点 + const cpx = (x1 + x2) / 2 - (y2 - y1) * curveness; + const cpy = (y1 + y2) / 2 - (x1 - x2) * curveness; + // 计算曲线上的中点 (t=0.5 时的贝塞尔曲线点) + const midX = x1 * 0.25 + cpx * 0.5 + x2 * 0.25; + const midY = y1 * 0.25 + cpy * 0.5 + y2 * 0.25; + midpoints.push({ + id: `line-label-${index}`, + midpoint: [midX, midY], + fromCountry, + toCountry, + }); + } + } + // 更新中点引用 + // lineMidpointsRef.current = midpoints; + return res; + }; + // 创建双层点效果 - 大点 + const createDualLayerPoint = ( + lastExit: LinesItemType, + isMainPath: boolean = true, + color?: string + ) => { + // 创建数据数组,用于两个散点图层 + const pointData = lastExit + ? [lastExit].map((v) => { + return { + name: v.name, + value: v.value, + datas: { + country_code: v.country_code, + color: v.color, // 添加颜色属性 + }, + }; + }) + : []; + // 根据是否是主路径设置不同的大小和颜色 + const outerSize = isMainPath ? 8 : 4; + const innerSize = isMainPath ? 4 : 2; + // 使用传入的颜色或从数据中获取颜色,如果都没有则使用默认颜色 + const outerColor = color || lastExit?.color || "#0ea5e9"; + const innerColor = "#FFFFFF"; // 白色内层 + return [ + { + // 外层蓝色点,带涟漪效果 + type: "effectScatter", + coordinateSystem: "geo", + zlevel: 3, + itemStyle: { + color: outerColor, + }, + symbol: "circle", + symbolSize: outerSize, + rippleEffect: { + period: 8, // 动画时间,值越小速度越快 + brushType: "stroke", // 波纹绘制方式 stroke + scale: 6, // 波纹圆环最大限制,值越大波纹越大 + brushWidth: 2, + }, + label: { + show: false, + }, + tooltip: { + show: false, + trigger: "item", + showContent: true, + alwaysShowContent: true, + formatter: (params: any) => { + // const countryCode = params.data.datas.country_code; + // const countryName = params.data.name; + // 创建自定义HTML提示框 + return `
嵌套加密
`; - }, - backgroundColor: "transparent", - borderWidth: 0, - }, - data: pointData, - } as echarts.SeriesOption, - { - // 内层白色点,不带涟漪效果 - type: "scatter", // 使用普通scatter,不带特效 - coordinateSystem: "geo", - zlevel: 4, // 确保在蓝色点上方 - color: innerColor, - symbol: "circle", - symbolSize: innerSize, - label: { - show: false, - }, - data: pointData, - } as echarts.SeriesOption, - ]; + }, + backgroundColor: "transparent", + borderWidth: 0, + }, + data: pointData, + } as echarts.SeriesOption, + { + // 内层白色点,不带涟漪效果 + type: "scatter", // 使用普通scatter,不带特效 + coordinateSystem: "geo", + zlevel: 4, // 确保在蓝色点上方 + color: innerColor, + symbol: "circle", + symbolSize: innerSize, + label: { + show: false, + }, + data: pointData, + } as echarts.SeriesOption, + ]; + }; + // 添加新方法:根据经纬度数组创建蓝色涟漪小点(不包含白色内层点) + const createRipplePointsFromCoordinates = ( + coordinates: [number, number][], + series: echarts.SeriesOption[] + ) => { + if (!coordinates || coordinates.length === 0) return; + // 使用selectedApp.color或默认蓝色 + const outerColor = "#01FF5E"; + // 只创建外层带涟漪效果的点 + series.push({ + type: "effectScatter", + coordinateSystem: "geo", + zlevel: 3, + color: outerColor, + symbol: "circle", + symbolSize: 6, + rippleEffect: { + period: 8, // 动画时间 + brushType: "stroke", // 波纹绘制方式 + scale: 6, // 波纹圆环最大限制 + brushWidth: 2, + }, + label: { + show: false, + }, + data: coordinates.map((coord) => ({ + name: "", // 可以根据需要添加名称 + value: coord, + })), + } as echarts.SeriesOption); + }; + // 创建路径点的双层效果 + const createPathPoints = ( + dataItems: LinesDataType[], + isMainPath: boolean = true, + color?: string + ) => { + // 创建数据数组 + const pointData = dataItems.map((dataItem: LinesDataType) => { + return { + name: dataItem[0].name, + value: geoCoordMap[dataItem[0].country_code], + datas: { + country_code: dataItem[0].country_code, + color: dataItem[0].color, // 添加颜色属性 + }, }; - // 创建路径点的双层效果 - const createPathPoints = ( - dataItems: LinesDataType[], - isMainPath: boolean = true - ) => { - // 创建数据数组 - const pointData = dataItems.map((dataItem: LinesDataType) => { - return { - name: dataItem[0].name, - value: geoCoordMap[dataItem[0].country_code], - datas: { - country_code: dataItem[0].country_code, - }, - }; - }); - // 根据是否是主路径设置不同的大小和颜色 - const outerSize = isMainPath ? 8 : 4; - const innerSize = isMainPath ? 4 : 2; - // Use selectedApp.color if available, otherwise default to blue - const outerColor = selectedApp?.color || "#0ea5e9"; // Use selectedApp.color with fallback - const innerColor = "#FFFFFF"; // 白色内层 - return [ - { - // 外层蓝色点,带涟漪效果 - type: "effectScatter", - coordinateSystem: "geo", - zlevel: 3, - color: outerColor, - symbol: "circle", - symbolSize: outerSize, - rippleEffect: { - period: 8, // 动画时间,值越小速度越快 - brushType: "stroke", // 波纹绘制方式 stroke - scale: 6, // 波纹圆环最大限制,值越大波纹越大 - brushWidth: 2, - }, - label: { - show: false, - }, - tooltip: { - show: false, - trigger: "item", - formatter: (params: any) => { - // const countryCode = params.data.datas.country_code; - // const countryName = params.data.name; - // 创建自定义HTML提示框 - return ` + }); + // 根据是否是主路径设置不同的大小和颜色 + const outerSize = isMainPath ? 8 : 4; + const innerSize = isMainPath ? 4 : 2; + // 使用传入的颜色或从数据中获取颜色,如果都没有则使用默认颜色 + const outerColor = color || dataItems[0]?.[0]?.color || "#0ea5e9"; + const innerColor = "#FFFFFF"; // 白色内层 + return [ + { + // 外层蓝色点,带涟漪效果 + type: "effectScatter", + coordinateSystem: "geo", + zlevel: 3, + itemStyle: { + color: outerColor, + }, + symbol: "circle", + symbolSize: outerSize, + rippleEffect: { + period: 8, // 动画时间,值越小速度越快 + brushType: "stroke", // 波纹绘制方式 stroke + scale: 6, // 波纹圆环最大限制,值越大波纹越大 + brushWidth: 2, + }, + label: { + show: false, + }, + tooltip: { + show: false, + trigger: "item", + formatter: (params: any) => { + // const countryCode = params.data.datas.country_code; + // const countryName = params.data.name; + // 创建自定义HTML提示框 + return `
嵌套加密
`; - }, - backgroundColor: "transparent", - borderWidth: 0, - }, - data: pointData, - } as echarts.SeriesOption, - { - // 内层白色点,不带涟漪效果 - type: "scatter", // 使用普通scatter,不带特效 - coordinateSystem: "geo", - zlevel: 4, // 确保在蓝色点上方 - color: innerColor, - symbol: "circle", - symbolSize: innerSize, - label: { - show: false, - }, - data: pointData, - } as echarts.SeriesOption, - ]; - }; - // 创建带自定义提示框的涟漪点 - const createRipplePointsWithTooltip = (ripplePoints: any) => { - // Use selectedApp.color if available, otherwise default to blue - const outerColor = selectedApp?.color || "#0ea5e9"; // Use selectedApp.color with fallback - return { - type: "effectScatter", - coordinateSystem: "geo", - zlevel: 3, - color: outerColor, - symbol: "circle", - symbolSize: 8, - rippleEffect: { - period: 8, - brushType: "stroke", - scale: 6, - brushWidth: 2, - }, - label: { - show: false, - formatter: (params: any) => { - console.log(params, "params"); - return `{${params.data.datas.country_code}|}`; - }, - }, - // 添加提示框配置 - tooltip: { - show: false, - trigger: "item", - formatter: (params: any) => { - // const countryCode = params.data.datas.country_code; - // const countryName = params.data.name; - // 创建自定义HTML提示框 - return ` + }, + backgroundColor: "transparent", + borderWidth: 0, + }, + data: pointData, + } as echarts.SeriesOption, + { + // 内层白色点,不带涟漪效果 + type: "scatter", // 使用普通scatter,不带特效 + coordinateSystem: "geo", + zlevel: 4, // 确保在蓝色点上方 + color: innerColor, + symbol: "circle", + symbolSize: innerSize, + label: { + show: false, + }, + data: pointData, + } as echarts.SeriesOption, + ]; + }; + // 创建带自定义提示框的涟漪点 + const createRipplePointsWithTooltip = (ripplePoints: any) => { + console.log(ripplePoints, "asdasdas"); + return { + type: "effectScatter", + coordinateSystem: "geo", + zlevel: 3, + // 使用函数动态设置每个点的颜色 + itemStyle: { + color: (params: any) => { + return params.data.datas?.color || "#0ea5e9"; // 使用点的颜色或默认颜色 + }, + }, + symbol: "circle", + symbolSize: 8, + rippleEffect: { + period: 8, + brushType: "stroke", + scale: 6, + brushWidth: 2, + }, + label: { + show: false, + formatter: (params: any) => { + console.log(params, "params"); + return `{${params.data.datas.country_code}|}`; + }, + }, + // 添加提示框配置 + tooltip: { + show: false, + trigger: "item", + formatter: (params: any) => { + // const countryCode = params.data.datas.country_code; + // const countryName = params.data.name; + // 创建自定义HTML提示框 + return `
嵌套加密
`; - }, - backgroundColor: "transparent", - borderWidth: 0, - }, - data: ripplePoints.map((point: any) => ({ - name: point.name, - value: point.value, - datas: { - country_code: point.country_code, - }, - })), - } as echarts.SeriesOption; - }; - // 连线 series - const getLianData = (series: echarts.SeriesOption[]) => { - const { solidData, otherLineList, ripplePoints } = getLine(); - // 如果有需要显示涟漪效果的点,添加它们 - if (ripplePoints.length > 0) { - // 添加带自定义提示框的外层蓝色点 - series.push(createRipplePointsWithTooltip(ripplePoints)); - // 添加内层白色点,不带涟漪效果 - series.push({ - type: "scatter", // 使用普通scatter,不带特效 - coordinateSystem: "geo", - zlevel: 4, // 确保在蓝色点上方 - color: "#FFFFFF", // 白色内层 - symbol: "circle", - symbolSize: 4, - label: { - show: false, - }, - data: ripplePoints.map((point) => ({ - name: point.name, - value: point.value, - datas: { - country_code: point.country_code, - }, - })), - } as echarts.SeriesOption); - } - solidData.forEach((item) => { - // 如果没有连线数据,则跳过 - if (item[1].length === 0) { - return; - } - const lastExit = item[1]?.[item[1].length - 1]?.[1] ?? null; - // 添加飞行线 - series.push({ - name: item[0], - type: "lines", - zlevel: 1, - label: { - show: false, // 不使用内置标签 - }, - // 飞行线特效 - effect: { - show: true, // 是否显示 - period: 4, // 特效动画时间 - trailLength: 0.7, // 特效尾迹长度。取从 0 到 1 的值,数值越大尾迹越长 - symbol: planePathImg, // 特效图形标记 - symbolSize: [10, 20], - }, - // 线条样式 - lineStyle: { - curveness: -0.4, // 飞线弧度 - type: "solid", // 飞线类型 - color: selectedApp?.color || "#0ea5e9", // Use selectedApp.color with fallback - width: 1.5, // 飞线宽度 - opacity: 0.1, - }, - data: convertData( - item[1] - ) as echarts.LinesSeriesOption["data"], - }); - // 添加路径点的双层效果 - const pathPoints = createPathPoints(item[1], true); - series.push(...pathPoints); - // 添加出口节点的双层效果 - if (lastExit) { - const exitNodes = createDualLayerPoint(lastExit, true); - series.push(...exitNodes); - } - }); - otherLineList.forEach((line: any) => { - line.forEach((item: any) => { - const lastExit = item[1]?.[item[1].length - 1]?.[1] ?? null; - // 添加虚线 - series.push({ - name: item[0], - type: "lines", - zlevel: 1, - label: { - show: false, - }, - // 线条样式 - lineStyle: { - curveness: -0.4, // 飞线弧度 - type: [5, 5], // 飞线类型 - color: "#F0FFA2", // 飞线颜色 - width: 0.5, // 飞线宽度 - opacity: 0.6, - }, - data: convertData( - item[1] - ) as echarts.LinesSeriesOption["data"], - }); - // 添加路径点的双层效果(次要路径) - const pathPoints = createPathPoints(item[1], false); - series.push(...pathPoints); - // 添加出口节点的双层效果(次要路径) - if (lastExit) { - const exitNodes = createDualLayerPoint(lastExit, false); - series.push(...exitNodes); - } - }); - }); - return true; - }; - // 主线tip series - const getMianLineTipData = (series: echarts.SeriesOption[] = []) => { - series.push( - // 柱状体的主干 - { - name: "solidTip", - type: "lines", - zlevel: 5, - effect: { - show: false, - symbolSize: 5, // 图标大小 - }, - lineStyle: { - width: 0, // 尾迹线条宽度 - color: "#F0FFA2", - opacity: 1, // 尾迹线条透明度 - curveness: 0, // 尾迹线条曲直度 - }, - label: { - show: true, - position: "end", - color: "#fff", - formatter: (parameters) => { - const isFr = parameters.value === "FR"; - console.log(parameters, "parameters"); - const name = isFr ? "权威节点团" : "待认证节点"; - return `{left|}{gap2|}{name${ - isFr ? "2" : "1" - }|${name}}{gap3|}{right|}`; - }, - rich: { - // left: { - // color: "transparent", - // height: 35, - // width: 8, - // align: "center", - // backgroundColor: { - // image: `data:image/svg+xml;charset=utf-8;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMSIgaGVpZ2h0PSI1OCIgdmlld0JveD0iMCAwIDExIDU4IiBmaWxsPSJub25lIj4KPHBhdGggZD0iTTExIDU2LjA4ODRIMVY0Ni4wODg0IiBzdHJva2U9IiNGRkZERDMiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIi8+CjxwYXRoIGQ9Ik0xIDExVjFIMTEiIHN0cm9rZT0iI0ZGRkREMyIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiLz4KPHBhdGggZD0iTTguNzI5NDkgMTlWMzkiIHN0cm9rZT0iI0ZGRkREMyIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiLz4KPC9zdmc+`, // 动态生成国旗图标 - // }, - // }, - gap1: { - height: 35, - width: 8, - }, + }, + backgroundColor: "transparent", + borderWidth: 0, + }, + data: ripplePoints.map((point: any) => ({ + name: point.name, + value: point.value, + datas: { + country_code: point.country_code, + color: point.color, // 添加颜色属性 + }, + })), + } as echarts.SeriesOption; + }; + // 连线 series + const getLianData = (series: echarts.SeriesOption[]) => { + const { solidData, otherLineList, ripplePoints } = getLine(); + // 如果有需要显示涟漪效果的点,添加它们 + if (ripplePoints.length > 0) { + // 添加带自定义提示框的外层蓝色点 + series.push(createRipplePointsWithTooltip(ripplePoints)); + // 添加内层白色点,不带涟漪效果 + series.push({ + type: "scatter", // 使用普通scatter,不带特效 + coordinateSystem: "geo", + zlevel: 4, // 确保在蓝色点上方 + color: "#FFFFFF", // 白色内层 + symbol: "circle", + symbolSize: 4, + label: { + show: false, + }, + data: ripplePoints.map((point) => ({ + name: point.name, + value: point.value, + datas: { + country_code: point.country_code, + color: point.color, // 添加颜色属性 + }, + })), + } as echarts.SeriesOption); + } + solidData.forEach((item) => { + console.log(item, "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"; // 从第一个点获取颜色,如果没有则使用默认颜色 - gap2: { - height: 35, - width: 6, - }, - name1: { - color: "#FF6B01", - align: "center", - lineHeight: 35, - fontSize: 18, - fontWeight: 600, - padding: [11, 16.52, 11, 16.52], - backgroundColor: "rgba(63, 6, 3, 0.5)", - }, - name2: { - color: "#37FF00", - align: "center", - lineHeight: 35, - fontSize: 18, - fontWeight: 600, - padding: [11, 16.52, 11, 16.52], - backgroundColor: "rgba(4, 59, 27, 0.5)", - }, - gap3: { - height: 35, - width: 8, - }, - // right: { - // color: "transparent", - // height: 35, - // width: 8, - // align: "center", - // backgroundColor: { - // image: `data:image/svg+xml;charset=utf-8;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNCIgaGVpZ2h0PSI1OCIgdmlld0JveD0iMCAwIDE0IDU4IiBmaWxsPSJub25lIj4KPHBhdGggZD0iTTEyLjczMDIgNDYuMDQzOVY1Ni4wNDM5SDIuNzMwMjIiIHN0cm9rZT0iI0ZGRkREMyIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiLz4KPHBhdGggZD0iTTIuNzMwMjIgMS4wNDM5NUgxMi43MzAyVjExLjA0MzkiIHN0cm9rZT0iI0ZGRkREMyIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiLz4KPHBhdGggZD0iTTEgMTkuMDQzOVYzOS4wNDM5IiBzdHJva2U9IiNGRkZERDMiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIi8+Cjwvc3ZnPg==`, // 动态生成国旗图标 - // }, - // }, - }, - backgroundColor: "transparent", - }, - silent: true, - data: mianLineData(mainToData), - } - ); - }; + // 添加飞行线 + series.push({ + name: item[0], + type: "lines", + zlevel: 1, + label: { + show: false, // 不使用内置标签 + }, + // 飞行线特效 + effect: { + show: true, // 是否显示 + period: 4, // 特效动画时间 + trailLength: 0.7, // 特效尾迹长度。取从 0 到 1 的值,数值越大尾迹越长 + symbol: planePathImg, // 特效图形标记 + symbolSize: [10, 20], + }, + // 线条样式 + lineStyle: { + curveness: -0.4, // 飞线弧度 + type: "solid", // 飞线类型 + color: pathColor, // 使用从数据中获取的颜色 + width: 1.5, // 飞线宽度 + opacity: 0.1, + }, + data: convertData(item[1]) as echarts.LinesSeriesOption["data"], + }); - // 创建A点和B点,并添加飞线和标签 - const createSpecialPoints = (series: echarts.SeriesOption[]) => { - // 定义点A和点B的坐标 - const pointA = [-42.604303, 71.706936]; - const pointB = [-106.346771, 56.130366]; - const newPointB = [pointB[0] + 14, pointB[1] + 10]; + // 添加路径点的双层效果 + const pathPoints = createPathPoints(item[1], true, pathColor); + series.push(...pathPoints); - // 添加A点 - 带涟漪效果的双层点 - series.push( - // 外层带涟漪效果的点 - { - type: "effectScatter", - coordinateSystem: "geo", - zlevel: 3, - color: "#FF6B01", // 橙色外层 - symbol: "circle", - symbolSize: 8, - rippleEffect: { - period: 8, // 动画时间 - brushType: "stroke", // 波纹绘制方式 - scale: 6, // 波纹圆环最大限制 - brushWidth: 2, - }, - label: { - show: true, - position: [10, -50], - formatter: () => { - return "{name1|待认证节点}"; - }, - rich: { - name1: { - color: "#FF6B01", - align: "center", - lineHeight: 35, - fontSize: 18, - fontWeight: 600, - padding: [11, 16.52, 11, 16.52], - backgroundColor: "rgba(63, 6, 3, 0.5)", - }, - }, - backgroundColor: "transparent", - }, - data: [ - { - name: "格陵兰", - value: pointA, - }, - ], - } as echarts.SeriesOption, + // 添加出口节点的双层效果 + if (lastExit) { + const exitNodes = createDualLayerPoint(lastExit, true, pathColor); + series.push(...exitNodes); + } + }); + otherLineList.forEach((line: any) => { + line.forEach((item: any) => { + const lastExit = item[1]?.[item[1].length - 1]?.[1] ?? null; + // 获取当前路径的颜色 + const pathColor = item[1]?.[0]?.[0]?.color || "#F0FFA2"; // 从第一个点获取颜色,如果没有则使用默认颜色 - // 内层白色点 - { - type: "scatter", // 普通scatter,不带特效 - coordinateSystem: "geo", - zlevel: 4, // 确保在外层点上方 - color: "#FFFFFF", // 白色内层 - symbol: "circle", - symbolSize: 4, - label: { - show: false, - }, - data: [ - { - name: "格陵兰", - value: pointA, - }, - ], - } as echarts.SeriesOption - ); + // 添加虚线 + series.push({ + name: item[0], + type: "lines", + zlevel: 1, + label: { + show: false, + }, + // 线条样式 + lineStyle: { + curveness: -0.4, // 飞线弧度 + type: [5, 5], // 飞线类型 + color: pathColor, // 使用从数据中获取的颜色 + width: 0.5, // 飞线宽度 + opacity: 0.6, + }, + data: convertData(item[1]) as echarts.LinesSeriesOption["data"], + }); + // 添加路径点的双层效果(次要路径) + const pathPoints = createPathPoints(item[1], false, pathColor); + series.push(...pathPoints); + // 添加出口节点的双层效果(次要路径) + if (lastExit) { + const exitNodes = createDualLayerPoint(lastExit, false, pathColor); + series.push(...exitNodes); + } + }); + }); + return true; + }; + // 主线tip series + const getMianLineTipData = (series: echarts.SeriesOption[] = []) => { + series.push( + // 柱状体的主干 + { + name: "solidTip", + type: "lines", + zlevel: 5, + effect: { + show: false, + symbolSize: 5, // 图标大小 + }, + lineStyle: { + width: 0, // 尾迹线条宽度 + color: "#F0FFA2", + opacity: 1, // 尾迹线条透明度 + curveness: 0, // 尾迹线条曲直度 + }, + label: { + show: true, + position: "end", + color: "#fff", + formatter: (parameters) => { + const isFr = parameters.value === "FR"; + console.log(parameters, "parameters"); + const name = isFr ? "权威节点团" : "待认证节点"; + return `{left|}{gap2|}{name${ + isFr ? "2" : "1" + }|${name}}{gap3|}{right|}`; + }, + rich: { + // left: { + // color: "transparent", + // height: 35, + // width: 8, + // align: "center", + // backgroundColor: { + // image: `data:image/svg+xml;charset=utf-8;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMSIgaGVpZ2h0PSI1OCIgdmlld0JveD0iMCAwIDExIDU4IiBmaWxsPSJub25lIj4KPHBhdGggZD0iTTExIDU2LjA4ODRIMVY0Ni4wODg0IiBzdHJva2U9IiNGRkZERDMiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIi8+CjxwYXRoIGQ9Ik0xIDExVjFIMTEiIHN0cm9rZT0iI0ZGRkREMyIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiLz4KPHBhdGggZD0iTTguNzI5NDkgMTlWMzkiIHN0cm9rZT0iI0ZGRkREMyIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiLz4KPC9zdmc+`, // 动态生成国旗图标 + // }, + // }, + gap1: { + height: 35, + width: 8, + }, + gap2: { + height: 35, + width: 6, + }, + name1: { + color: "#FF6B01", + align: "center", + lineHeight: 35, + fontSize: 18, + fontWeight: 600, + padding: [11, 16.52, 11, 16.52], + backgroundColor: "rgba(63, 6, 3, 0.5)", + }, + name2: { + color: "#37FF00", + align: "center", + lineHeight: 35, + fontSize: 18, + fontWeight: 600, + padding: [11, 16.52, 11, 16.52], + backgroundColor: "rgba(4, 59, 27, 0.5)", + }, + gap3: { + height: 35, + width: 8, + }, + // right: { + // color: "transparent", + // height: 35, + // width: 8, + // align: "center", + // backgroundColor: { + // image: `data:image/svg+xml;charset=utf-8;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNCIgaGVpZ2h0PSI1OCIgdmlld0JveD0iMCAwIDE0IDU4IiBmaWxsPSJub25lIj4KPHBhdGggZD0iTTEyLjczMDIgNDYuMDQzOVY1Ni4wNDM5SDIuNzMwMjIiIHN0cm9rZT0iI0ZGRkREMyIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiLz4KPHBhdGggZD0iTTIuNzMwMjIgMS4wNDM5NUgxMi43MzAyVjExLjA0MzkiIHN0cm9rZT0iI0ZGRkREMyIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiLz4KPHBhdGggZD0iTTEgMTkuMDQzOVYzOS4wNDM5IiBzdHJva2U9IiNGRkZERDMiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIi8+Cjwvc3ZnPg==`, // 动态生成国旗图标 + // }, + // }, + }, + backgroundColor: "transparent", + }, + silent: true, + data: mianLineData(mainToData), + } + ); + }; + // 创建A点和B点,并添加飞线和标签 + const createSpecialPoints = (series: echarts.SeriesOption[]) => { + // 定义点A和点B的坐标 + const pointA = [-42.604303, 71.706936]; + const pointB = [-106.346771, 56.130366]; + const newPointB = [pointB[0] + 14, pointB[1] + 10]; + // 添加A点 - 带涟漪效果的双层点 + series.push( + // 外层带涟漪效果的点 + { + type: "effectScatter", + coordinateSystem: "geo", + zlevel: 3, + color: "#FF6B01", // 橙色外层 + symbol: "circle", + symbolSize: 8, + rippleEffect: { + period: 8, // 动画时间 + brushType: "stroke", // 波纹绘制方式 + scale: 6, // 波纹圆环最大限制 + brushWidth: 2, + }, + label: { + show: true, + position: [10, -50], + formatter: () => { + return "{name1|待认证节点}"; + }, + rich: { + name1: { + color: "#FF6B01", + align: "center", + lineHeight: 35, + fontSize: 18, + fontWeight: 600, + padding: [11, 16.52, 11, 16.52], + backgroundColor: "rgba(63, 6, 3, 0.5)", + }, + }, + backgroundColor: "transparent", + }, + data: [ + { + name: "格陵兰", + value: pointA, + }, + ], + } as echarts.SeriesOption, + // 内层白色点 + { + type: "scatter", // 普通scatter,不带特效 + coordinateSystem: "geo", + zlevel: 4, // 确保在外层点上方 + color: "#FFFFFF", // 白色内层 + symbol: "circle", + symbolSize: 4, + label: { + show: false, + }, + data: [ + { + name: "格陵兰", + value: pointA, + }, + ], + } as echarts.SeriesOption + ); + // 添加B点 - 大型圆形区域 + series.push({ + type: "scatter", + coordinateSystem: "geo", + zlevel: 2, + color: "rgba(55, 255, 0, 0.50)", // 半透明绿色 + symbol: "circle", + symbolSize: 150, // 大尺寸圆形 + label: { + show: true, + position: [-70, -30], + formatter: () => { + return "{name2|权威节点团}"; + }, + rich: { + name2: { + color: "#37FF00", + align: "center", + lineHeight: 35, + fontSize: 18, + fontWeight: 600, + padding: [11, 16.52, 11, 16.52], + backgroundColor: "rgba(4, 59, 27, 0.5)", + }, + }, + backgroundColor: "transparent", + }, + data: [ + { + name: "加拿大", + value: pointB, + }, + ], + } as echarts.SeriesOption); + // 添加A到B的飞线(无特效) + series.push({ + type: "lines", + zlevel: 1, + effect: { + show: false, // 关闭特效 + }, + lineStyle: { + curveness: -0.4, // 飞线弧度 + type: "solid", + color: "#FEAA18", // 飞线颜色 + width: 1.5, + opacity: 0.8, + }, + data: [ + { + coords: [pointA, newPointB], // 从A点到B点 + }, + ], + } as echarts.SeriesOption); + // 计算飞线中点坐标(考虑曲率) + const x1 = pointA[0]; + const y1 = pointA[1]; + const x2 = newPointB[0]; + const y2 = newPointB[1]; + const curveness = -0.4; + // 计算控制点 + const cpx = (x1 + x2) / 2 - (y2 - y1) * curveness; + const cpy = (y1 + y2) / 2 - (x1 - x2) * curveness; + // 计算曲线上的中点 (t=0.5 时的贝塞尔曲线点) + const midX = x1 * 0.25 + cpx * 0.5 + x2 * 0.25; + const midY = y1 * 0.25 + cpy * 0.5 + y2 * 0.25; + // 将中点添加到 lineMidpointsRef 中,以便使用 DOM 方式创建标签 + lineMidpointsRef.current.push({ + id: "special-line-label", + midpoint: [midX, midY], + fromCountry: "A", + toCountry: "B", + }); + return series; + }; + const getOption = () => { + const series: echarts.SeriesOption[] = []; + getLianData(series); + // getMianLineTipData(series); // 添加主线tip 暂时隐藏 + if (tooltipType === "PASS_AUTHENTICATION") { + createSpecialPoints(series); // 添加特殊点和飞线 + if (newHomeProxies[0]?.authenticationPoint) { + createRipplePointsFromCoordinates( + newHomeProxies[0]?.authenticationPoint || [], + series + ); + } + } - // 添加B点 - 大型圆形区域 - series.push({ - type: "scatter", - coordinateSystem: "geo", - zlevel: 2, - color: "rgba(55, 255, 0, 0.50)", // 半透明绿色 - symbol: "circle", - symbolSize: 150, // 大尺寸圆形 - label: { - show: true, - position: [-70, -30], - formatter: () => { - return "{name2|权威节点团}"; - }, - rich: { - name2: { - color: "#37FF00", - align: "center", - lineHeight: 35, - fontSize: 18, - fontWeight: 600, - padding: [11, 16.52, 11, 16.52], - backgroundColor: "rgba(4, 59, 27, 0.5)", - }, - }, - backgroundColor: "transparent", - }, - data: [ - { - name: "加拿大", - value: pointB, - }, - ], - } as echarts.SeriesOption); - - // 添加A到B的飞线(无特效) - series.push({ - type: "lines", - zlevel: 1, - effect: { - show: false, // 关闭特效 - }, - lineStyle: { - curveness: -0.4, // 飞线弧度 - type: "solid", - color: "#FEAA18", // 飞线颜色 - width: 1.5, - opacity: 0.8, - }, - data: [ - { - coords: [pointA, newPointB], // 从A点到B点 - }, - ], - } as echarts.SeriesOption); - - // 计算飞线中点坐标(考虑曲率) - const x1 = pointA[0]; - const y1 = pointA[1]; - const x2 = newPointB[0]; - const y2 = newPointB[1]; - const curveness = -0.4; - - // 计算控制点 - const cpx = (x1 + x2) / 2 - (y2 - y1) * curveness; - const cpy = (y1 + y2) / 2 - (x1 - x2) * curveness; - - // 计算曲线上的中点 (t=0.5 时的贝塞尔曲线点) - const midX = x1 * 0.25 + cpx * 0.5 + x2 * 0.25; - const midY = y1 * 0.25 + cpy * 0.5 + y2 * 0.25; - - // 将中点添加到 lineMidpointsRef 中,以便使用 DOM 方式创建标签 - lineMidpointsRef.current.push({ - id: "special-line-label", - midpoint: [midX, midY], - fromCountry: "A", - toCountry: "B", - }); - - return series; - }; - - const getOption = () => { - const series: echarts.SeriesOption[] = []; - getLianData(series); - // getMianLineTipData(series); // 添加主线tip 暂时隐藏 - createSpecialPoints(series); // 添加特殊点和飞线 - - const option = { - backgroundColor: "transparent", - // 全局提示框配置 - tooltip: { - show: true, - trigger: "item", - enterable: true, - confine: true, // 保持提示框在图表范围内 - appendToBody: true, // 将提示框附加到body以获得更好的定位 - // position: function(pos:any, params, dom, rect, size) { - position: function (pos: any) { - // 自定义定位逻辑(如果需要) - return [pos[0] + 10, pos[1] - 50]; // 从光标偏移 - }, - }, - // 底图样式 - geo: { - map: "world", // 地图类型 - roam: true, // 是否开启缩放 - zoom: 1, // 初始缩放大小 - // center: [11.3316626, 19.5845024], // 地图中心点 - layoutCenter: ["50%", "50%"], //地图位置 - scaleLimit: { - // 缩放等级 - min: 1, - max: 3, - }, - label: { - show: false, - }, - nameMap: countryNameMap, // 自定义地区的名称映射 - // 三维地理坐标系样式 - itemStyle: { - areaColor: "#020617", // 修改为要求的填充颜色 - borderColor: "#cbd5e1", // 修改为要求的边框颜色 - borderWidth: 1, // 边框宽度 - borderType: "dashed", // 修改为点线边框 - }, - emphasis: { - itemStyle: { - areaColor: "#172554", // 修改为鼠标悬停时的填充颜色 - borderColor: "#0ea5e9", // 修改为鼠标悬停时的边框颜色 - borderWidth: 1.2, // 修改为鼠标悬停时的边框宽度 - borderType: "solid", // 修改为实线边框 - }, - label: false, - }, - tooltip: { - show: true, - trigger: "item", - triggerOn: "click", // 提示框触发的条件 - enterable: true, // 鼠标是否可进入提示框浮层中,默认为false,如需详情内交互,如添加链接,按钮,可设置为 true - backgroundColor: "rgba(0,0,0,0.8)", - borderColor: "rgba(0,0,0,0.2)", - textStyle: { - color: "#fff", - }, - formatter: (parameters: { - name: string; - data: - | { - name: string; - datas: { tradingCountry: string }; - } - | undefined; - }) => { - if (parameters.data?.name) - return parameters.data.name; - return parameters.name; - }, - }, - }, - series: series, - }; - return option; - }; - - // 创建DOM标签 - const createDOMLabels = () => { - // 清除现有标签 - if (labelContainerRef.current) { - labelContainerRef.current.innerHTML = ""; - labelsRef.current = []; - } else { - // 创建标签容器 - const container = document.createElement("div"); - container.className = "line-labels-container"; - container.style.position = "absolute"; - container.style.top = "0"; - container.style.left = "0"; - container.style.pointerEvents = "none"; - container.style.zIndex = "1000"; - container.style.width = "100%"; - container.style.height = "100%"; - container.style.overflow = "hidden"; - - // 添加到地图容器 - const chartDom = document.getElementById("screenGeo"); - if (chartDom) { - chartDom.style.position = "relative"; - chartDom.appendChild(container); - labelContainerRef.current = container; - } - } - - // 创建新标签 - lineMidpointsRef.current.forEach((point, index) => { - const label = document.createElement("div"); - label.id = point.id; - label.className = "line-label"; - label.style.position = "absolute"; - label.style.textAlign = "center"; - label.style.transform = "translate(-50%, -50%)"; - label.style.whiteSpace = "nowrap"; - label.style.pointerEvents = "none"; - label.style.zIndex = "1001"; - - // 特殊线标签(A到B的线) - if (point.id === "special-line-label") { - label.style.backgroundColor = "#8B3700"; - label.style.color = "#FFB27A"; - label.style.padding = "5px 10px"; - label.style.borderRadius = "4px"; - label.style.fontSize = "18px"; - label.style.fontWeight = "normal"; - label.textContent = "SS签名"; - } - // 其他线标签 - else { - label.style.backgroundColor = "#8B3700"; - label.style.color = "#FFB27A"; - label.style.padding = "5px 10px"; - label.style.borderRadius = "4px"; - label.style.fontSize = "18px"; - label.style.fontWeight = "normal"; - label.textContent = "SS签名"; - } - - // 添加到容器 - labelContainerRef.current?.appendChild(label); - labelsRef.current.push(label); - }); - - // 更新标签位置 - updateLabelPositions(); - }; - - // 更新标签位置 - const updateLabelPositions = () => { - if (!proxyGeoRef.current || !labelContainerRef.current) return; - - lineMidpointsRef.current.forEach((point, index) => { - const label = labelsRef.current[index]; - if (!label) return; - - const pixelPoint = proxyGeoRef.current?.convertToPixel( - "geo", - point.midpoint - ); - if (pixelPoint && Array.isArray(pixelPoint)) { - label.style.left = `${pixelPoint[0]}px`; - label.style.top = `${pixelPoint[1]}px`; - } - }); - }; - - const handleResize = () => { - proxyGeoRef.current?.resize(); - updateLabelPositions(); - }; - - useEffect(() => { - preMainToData.current?.some( - (item, index) => - item.country_code !== mainToData[index]?.country_code - ) && proxyGeoRef.current?.clear(); - preMainToData.current = mainToData; - const option = getOption(); - proxyGeoRef.current?.setOption(option); - - // 创建DOM标签 - setTimeout(createDOMLabels, 100); - }, [screenData, mainToData]); - - useEffect(() => { - const chartDom = document.getElementById("screenGeo"); - proxyGeoRef.current = echarts.init(chartDom); - echarts.registerMap( - "world", - worldGeoJson as unknown as Parameters< - typeof echarts.registerMap - >[1] - ); - const option = getOption(); - option && proxyGeoRef.current?.setOption(option); - - // 添加地图交互事件监听器 - proxyGeoRef.current?.on("georoam", updateLabelPositions); - - // 页面resize时触发 - window.addEventListener("resize", handleResize); - return () => { - window.removeEventListener("resize", handleResize); - proxyGeoRef.current?.off("georoam", updateLabelPositions); - - // 清理DOM标签 - if (labelContainerRef.current) { - labelContainerRef.current.remove(); - labelContainerRef.current = null; - labelsRef.current = []; - } - - proxyGeoRef.current?.dispose(); - proxyGeoRef.current = null; - }; - }, []); - - useEffect(() => { - if (tooltipClosed) { - createCustomTooltip(); - } - }, [tooltipClosed, tooltipType]); - - return ( -
-
-
+ const option = { + backgroundColor: "transparent", + // 全局提示框配置 + tooltip: { + show: true, + trigger: "item", + enterable: true, + confine: true, // 保持提示框在图表范围内 + appendToBody: true, // 将提示框附加到body以获得更好的定位 + // position: function(pos:any, params, dom, rect, size) { + position: function (pos: any) { + // 自定义定位逻辑(如果需要) + return [pos[0] + 10, pos[1] - 50]; // 从光标偏移 + }, + }, + // 底图样式 + geo: { + map: "world", // 地图类型 + roam: true, // 是否开启缩放 + zoom: 1, // 初始缩放大小 + // center: [11.3316626, 19.5845024], // 地图中心点 + layoutCenter: ["50%", "50%"], //地图位置 + scaleLimit: { + // 缩放等级 + min: 1, + max: 3, + }, + label: { + show: false, + }, + nameMap: countryNameMap, // 自定义地区的名称映射 + // 三维地理坐标系样式 + itemStyle: { + areaColor: "#020617", // 修改为要求的填充颜色 + borderColor: "#cbd5e1", // 修改为要求的边框颜色 + borderWidth: 1, // 边框宽度 + borderType: "dashed", // 修改为点线边框 + }, + emphasis: { + itemStyle: { + areaColor: "#172554", // 修改为鼠标悬停时的填充颜色 + borderColor: "#0ea5e9", // 修改为鼠标悬停时的边框颜色 + borderWidth: 1.2, // 修改为鼠标悬停时的边框宽度 + borderType: "solid", // 修改为实线边框 + }, + label: false, + }, + tooltip: { + show: true, + trigger: "item", + triggerOn: "click", // 提示框触发的条件 + enterable: true, // 鼠标是否可进入提示框浮层中,默认为false,如需详情内交互,如添加链接,按钮,可设置为 true + backgroundColor: "rgba(0,0,0,0.8)", + borderColor: "rgba(0,0,0,0.2)", + textStyle: { + color: "#fff", + }, + formatter: (parameters: { + name: string; + data: + | { + name: string; + datas: { tradingCountry: string }; + } + | undefined; + }) => { + if (parameters.data?.name) return parameters.data.name; + return parameters.name; + }, + }, + }, + series: series, + }; + return option; + }; + // 创建DOM标签 + const createDOMLabels = () => { + // 清除现有标签 + if (labelContainerRef.current) { + labelContainerRef.current.innerHTML = ""; + labelsRef.current?.forEach((item) => item?.remove()); + labelsRef.current = []; + } else { + // 创建标签容器 + const container = document.createElement("div"); + container.className = "line-labels-container"; + container.style.position = "absolute"; + container.style.top = "0"; + container.style.left = "0"; + container.style.pointerEvents = "none"; + container.style.zIndex = "1000"; + container.style.width = "100%"; + container.style.height = "100%"; + container.style.overflow = "hidden"; + // 添加到地图容器 + const chartDom = document.getElementById("screenGeo"); + if (chartDom) { + chartDom.style.position = "relative"; + chartDom.appendChild(container); + labelContainerRef.current = container; + } + } + // 创建新标签 + lineMidpointsRef.current.forEach((point, index) => { + const label = document.createElement("div"); + label.id = point.id; + label.className = "line-label"; + label.style.position = "absolute"; + label.style.textAlign = "center"; + label.style.transform = "translate(-50%, -50%)"; + label.style.whiteSpace = "nowrap"; + label.style.pointerEvents = "none"; + label.style.zIndex = "1001"; + // 特殊线标签(A到B的线) + if (point.id === "special-line-label") { + label.style.backgroundColor = "#8B3700"; + label.style.color = "#FFB27A"; + label.style.padding = "5px 10px"; + label.style.borderRadius = "4px"; + label.style.fontSize = "18px"; + label.style.fontWeight = "normal"; + label.textContent = "SS签名"; + } + // 其他线标签 + else { + label.style.backgroundColor = "#8B3700"; + label.style.color = "#FFB27A"; + label.style.padding = "5px 10px"; + label.style.borderRadius = "4px"; + label.style.fontSize = "18px"; + label.style.fontWeight = "normal"; + label.textContent = "SS签名"; + } + // 添加到容器 + labelContainerRef.current?.appendChild(label); + labelsRef.current.push(label); + }); + // 更新标签位置 + updateLabelPositions(); + }; + // 更新标签位置 + const updateLabelPositions = () => { + if (!proxyGeoRef.current || !labelContainerRef.current) return; + lineMidpointsRef.current.forEach((point, index) => { + const label = labelsRef.current[index]; + if (!label) return; + const pixelPoint = proxyGeoRef.current?.convertToPixel( + "geo", + point.midpoint ); - } + if (pixelPoint && Array.isArray(pixelPoint)) { + label.style.left = `${pixelPoint[0]}px`; + label.style.top = `${pixelPoint[1]}px`; + } + }); + }; + const handleResize = () => { + proxyGeoRef.current?.resize(); + updateLabelPositions(); + }; + useEffect(() => { + preMainToData.current?.some( + (item, index) => item.country_code !== mainToData[index]?.country_code + ) && proxyGeoRef.current?.clear(); + preMainToData.current = mainToData; + const option = getOption(); + proxyGeoRef.current?.setOption(option); + // 创建DOM标签 + setTimeout(createDOMLabels, 100); + }, [newHomeProxies, mainToData]); + useEffect(() => { + const chartDom = document.getElementById("screenGeo"); + proxyGeoRef.current = echarts.init(chartDom); + echarts.registerMap( + "world", + worldGeoJson as unknown as Parameters[1] + ); + const option = getOption(); + option && proxyGeoRef.current?.setOption(option); + // 添加地图交互事件监听器 + proxyGeoRef.current?.on("georoam", updateLabelPositions); + // 页面resize时触发 + window.addEventListener("resize", handleResize); + return () => { + window.removeEventListener("resize", handleResize); + proxyGeoRef.current?.off("georoam", updateLabelPositions); + // 清理DOM标签 + if (labelContainerRef.current) { + labelContainerRef.current.remove(); + labelContainerRef.current = null; + labelsRef.current = []; + } + proxyGeoRef.current?.dispose(); + proxyGeoRef.current = null; + }; + }, []); + useEffect(() => { + if (tooltipType !== "PASS_AUTHENTICATION") { + lineMidpointsRef.current = []; + } + if (tooltipClosed) { + if (tooltipType === "NESTED_ENCRYPTION") { + createCustomTooltip(); + } + if (tooltipType === "TRAFFIC_OBFUSCATION") { + createCustomTooltip2(); + } + } else { + customTooltipRef.current?.remove(); + customTooltip2Ref.current?.remove(); + customTooltipRef.current = null; + customTooltip2Ref.current = null; + } + return () => { + customTooltipRef.current?.remove(); + customTooltip2Ref.current?.remove(); + customTooltipRef.current = null; + customTooltip2Ref.current = null; + }; + }, [tooltipClosed, tooltipType]); + return ( +
+
+
+ ); + } ); diff --git a/src/pages/anti-forensics-forwarding/data/mockData.ts b/src/pages/anti-forensics-forwarding/data/mockData.ts new file mode 100644 index 0000000..0348841 --- /dev/null +++ b/src/pages/anti-forensics-forwarding/data/mockData.ts @@ -0,0 +1,447 @@ + +export const TRAFFIC_OBFUSCATION = { + type: "NESTED_ENCRYPTION", + name: "流量混淆", + code: "BR", + data: [ + { + country_code: "gl", + ingress_country_code: "za", + }, + { + country_code: "za", + ingress_country_code: "dz", + }, + { + country_code: "dz", + ingress_country_code: "ru", + }, + { + country_code: "dz", + ingress_country_code: "cn", + }, + ], + isLine: true, +}; +export const NESTED_ENCRYPTION = { + type: "NESTED_ENCRYPTION", + name: "嵌套加密", + code: "GL", + data: [ + { + country_code: "gl", + ingress_country_code: "br", + }, + { + country_code: "br", + ingress_country_code: "dz", + }, + { + country_code: "dz", + ingress_country_code: "ru", + }, + { + country_code: "dz", + ingress_country_code: "cn", + }, + ], + isLine: true, +}; + +export const DYNAMIC_ROUTE_GENERATOR = [ + { + type: "DYNAMIC_ROUTE_GENERATOR", + name: "动态路由生成", + data: [ + { + country_code: "us", + ingress_country_code: "ca", + }, + { + country_code: "ca", + ingress_country_code: "gl", + }, + { + country_code: "gl", + ingress_country_code: "by", + }, + { + country_code: "dz", + ingress_country_code: "cn", + }, + ], + color: "#48D3D5", + isLine: true, + }, + { + type: "DYNAMIC_ROUTE_GENERATOR", + name: "动态路由生成2", + data: [ + { + country_code: "br", + ingress_country_code: "ml", + }, + { + country_code: "ml", + ingress_country_code: "ly", + }, + { + country_code: "ly", + ingress_country_code: "cn", + }, + { + country_code: "cn", + ingress_country_code: "ru", + }, + ], + color: "#50FE35", + isLine: true, + }, +]; + +export const APP_DIVERSION = [ + { + name: "Netflix", + color: "#DC2626", + data: [ + { + country_code: "mg", + ingress_country_code: "ru", + }, + { + country_code: "ru", + ingress_country_code: "fr", + }, + { + country_code: "fr", + ingress_country_code: "br", + }, + { + country_code: "br", + ingress_country_code: "us", + }, + ], + isLine: true, + }, + { + name: "Spotify", + color: "#22C55E", + data: [ + { + country_code: "jp", + ingress_country_code: "au", + }, + { + country_code: "au", + ingress_country_code: "za", + }, + { + country_code: "za", + ingress_country_code: "de", + }, + { + country_code: "de", + ingress_country_code: "ca", + }, + ], + isLine: true, + }, + { + name: "Instagram", + color: "#8B5CF6", + data: [ + { + country_code: "it", + ingress_country_code: "in", + }, + { + country_code: "in", + ingress_country_code: "mx", + }, + { + country_code: "mx", + ingress_country_code: "se", + }, + { + country_code: "se", + ingress_country_code: "sg", + }, + ], + isLine: true, + }, + { + name: "Telegram", + color: "#2563EB", + data: [ + { + country_code: "ar", + ingress_country_code: "nl", + }, + { + country_code: "nl", + ingress_country_code: "kr", + }, + { + country_code: "kr", + ingress_country_code: "eg", + }, + { + country_code: "eg", + ingress_country_code: "nz", + }, + ], + isLine: true, + }, + { + name: "Google", + color: "#3B82F6", + data: [ + { + country_code: "ch", + ingress_country_code: "br", + }, + { + country_code: "br", + ingress_country_code: "hk", + }, + { + country_code: "hk", + ingress_country_code: "no", + }, + { + country_code: "no", + ingress_country_code: "ae", + }, + ], + isLine: true, + }, + { + name: "Gmail", + color: "#22C55E", + data: [ + { + country_code: "es", + ingress_country_code: "cn", + }, + { + country_code: "cn", + ingress_country_code: "co", + }, + { + country_code: "co", + ingress_country_code: "fi", + }, + { + country_code: "fi", + ingress_country_code: "id", + }, + ], + isLine: true, + }, + { + name: "Amazon", + color: "#EAB308", + data: [ + { + country_code: "gb", + ingress_country_code: "th", + }, + { + country_code: "th", + ingress_country_code: "cl", + }, + { + country_code: "cl", + ingress_country_code: "be", + }, + { + country_code: "be", + ingress_country_code: "ph", + }, + ], + isLine: true, + }, + { + name: "Ebay", + color: "#3B82F6", + data: [ + { + country_code: "pl", + ingress_country_code: "my", + }, + { + country_code: "my", + ingress_country_code: "pe", + }, + { + country_code: "pe", + ingress_country_code: "dk", + }, + { + country_code: "dk", + ingress_country_code: "ng", + }, + ], + isLine: true, + }, + { + name: "AppleNews", + color: "#EF4444", + data: [ + { + country_code: "ie", + ingress_country_code: "vn", + }, + { + country_code: "vn", + ingress_country_code: "ma", + }, + { + country_code: "ma", + ingress_country_code: "at", + }, + { + country_code: "at", + ingress_country_code: "tw", + }, + ], + isLine: true, + }, + { + name: "CNN", + color: "#EF4444", + data: [ + { + country_code: "ua", + ingress_country_code: "sa", + }, + { + country_code: "sa", + ingress_country_code: "gr", + }, + { + country_code: "gr", + ingress_country_code: "pk", + }, + { + country_code: "pk", + ingress_country_code: "pt", + }, + ], + isLine: true, + }, + { + name: "Browser", + color: "#8B5CF6", + data: [ + { + country_code: "il", + ingress_country_code: "ro", + }, + { + country_code: "ro", + ingress_country_code: "nz", + }, + { + country_code: "nz", + ingress_country_code: "tr", + }, + { + country_code: "tr", + ingress_country_code: "ca", + }, + ], + isLine: true, + }, + { + name: "YouTube", + color: "#EF4444", + data: [ + { + country_code: "cz", + ingress_country_code: "sg", + }, + { + country_code: "sg", + ingress_country_code: "ar", + }, + { + country_code: "ar", + ingress_country_code: "hu", + }, + { + country_code: "hu", + ingress_country_code: "jp", + }, + ], + isLine: true, + }, + { + name: "Facebook", + color: "#3B82F6", + data: [ + { + country_code: "is", + ingress_country_code: "za", + }, + { + country_code: "za", + ingress_country_code: "mx", + }, + { + country_code: "mx", + ingress_country_code: "it", + }, + { + country_code: "it", + ingress_country_code: "kr", + }, + ], + isLine: true, + }, +]; + +export const PASS_AUTHENTICATION = { + type: "PASS_AUTHENTICATION", + name: "通行认证", + authenticationPoint: [ + [-103.346771, 54.130366], + [-120.346771, 52.130366], + [-108.346771, 48.130366], + [-98.346771, 46.130366], + [-106.346771, 48.450366], + [-101.346771, 53.130366], + [-123.346771, 58.130366], + [-111.346771, 65.443366], + [-108.346771, 54.130366], + [-116.346771, 59.130366], + [-97.346771, 61.130366], + [-95.346771, 63.130366], + [-113.346771, 58.840366], + [-99.346771, 59.130366], + [-102.346771, 68.130366], + ], + data: [ + { + country_code: "gl", + ingress_country_code: "dz", + }, + { + country_code: "br", + ingress_country_code: "dz", + }, + { + country_code: "dz", + ingress_country_code: "ru", + }, + { + country_code: "dz", + ingress_country_code: "cn", + }, + { + country_code: "ru", + ingress_country_code: "za", + }, + ], + isLine: true, +}; diff --git a/src/pages/anti-forensics-forwarding/index.scss b/src/pages/anti-forensics-forwarding/index.scss index 4e71d3b..57511fb 100644 --- a/src/pages/anti-forensics-forwarding/index.scss +++ b/src/pages/anti-forensics-forwarding/index.scss @@ -131,6 +131,11 @@ width: 312.221px; } + .line-img-left{ + width: 216.86px; + margin-top: 30px; + } + .fill { width: 9.165px; height: 9.165px; @@ -141,6 +146,17 @@ top: 77.5px; z-index: 99; } + + .fill-left { + width: 9.165px; + height: 9.165px; + border-radius: 50%; + background-color: #18E4FF; + position: absolute; + right:210.5px; + top: 82.5px; + z-index: 99; + } } diff --git a/src/pages/anti-forensics-forwarding/index.tsx b/src/pages/anti-forensics-forwarding/index.tsx index 7286ced..c2bfae7 100644 --- a/src/pages/anti-forensics-forwarding/index.tsx +++ b/src/pages/anti-forensics-forwarding/index.tsx @@ -30,501 +30,270 @@ import FacebookSvg from "@/assets/svg/anti-forensics-forwarding/Facebook.svg?rea import FacebookActiveSvg from "@/assets/svg/anti-forensics-forwarding/FacebookActive.svg?react"; import { RootState } from "@/store"; +import { + TRAFFIC_OBFUSCATION, + NESTED_ENCRYPTION, + DYNAMIC_ROUTE_GENERATOR, + APP_DIVERSION, + PASS_AUTHENTICATION, +} from "./data/mockData"; + import "./index.scss"; -import { color } from "echarts"; +import { App } from "antd"; export const DIALOGTYPE = { - ADDNode: { - title: "添加节点", - desc: "", - successText: "添加", - }, - AddNetwork: { - title: "构建网络", - desc: "", - successText: "构建", - }, + ADDNode: { + title: "添加节点", + desc: "", + successText: "添加", + }, + AddNetwork: { + title: "构建网络", + desc: "", + successText: "构建", + }, }; export const NODEDIALOGTYPE = { - ClearFailNode: { - title: "清除掉线节点", - desc: "", - successText: "清除", - }, - ClearWargingNode: { - title: "恶意节点", - desc: "", - successText: "清除", - }, + ClearFailNode: { + title: "清除掉线节点", + desc: "", + successText: "清除", + }, + ClearWargingNode: { + title: "恶意节点", + desc: "", + successText: "清除", + }, }; export const Apps = [ - { - name: "Netflix", - icon: NetflixSvg, - activeIcon: NetflixActiveSvg, - color: "#DC2626", - jumpList: { - data: [ - { - country_code: "mg", - ingress_country_code: "ru", - }, - { - country_code: "ru", - ingress_country_code: "fr", - }, - { - country_code: "fr", - ingress_country_code: "br", - }, - { - country_code: "br", - ingress_country_code: "us", - }, - ], - isLine: true, - name: "Netflix", - }, - }, - { - name: "Spotify", - icon: SpotifySvg, - activeIcon: SpotifyActiveSvg, - color: "#22C55E", - jumpList: { - data: [ - { - country_code: "mg", - ingress_country_code: "ru", - }, - { - country_code: "ru", - ingress_country_code: "fr", - }, - { - country_code: "fr", - ingress_country_code: "br", - }, - { - country_code: "br", - ingress_country_code: "us", - }, - ], - isLine: true, - name: "Spotify", - }, - }, - { - name: "Instagram", - icon: InstagramSvg, - activeIcon: InstagramActiveSvg, - color: "#8B5CF6", - jumpList: { - data: [ - { - country_code: "mg", - ingress_country_code: "ru", - }, - { - country_code: "ru", - ingress_country_code: "fr", - }, - { - country_code: "fr", - ingress_country_code: "br", - }, - { - country_code: "br", - ingress_country_code: "us", - }, - ], - isLine: true, - name: "Instagram", - }, - }, - { - name: "Telegram", - icon: TelegramSvg, - activeIcon: TelegramActiveSvg, - color: "#2563EB", - jumpList: { - data: [ - { - country_code: "mg", - ingress_country_code: "ru", - }, - { - country_code: "ru", - ingress_country_code: "fr", - }, - { - country_code: "fr", - ingress_country_code: "br", - }, - { - country_code: "br", - ingress_country_code: "us", - }, - ], - isLine: true, - name: "Telegram", - }, - }, - { - name: "Google", - icon: GoogleSvg, - activeIcon: GoogleActiveSvg, - color: "#3B82F6", - jumpList: { - data: [ - { - country_code: "mg", - ingress_country_code: "ru", - }, - { - country_code: "ru", - ingress_country_code: "fr", - }, - { - country_code: "fr", - ingress_country_code: "br", - }, - { - country_code: "br", - ingress_country_code: "us", - }, - ], - isLine: true, - name: "Google", - }, - }, - { - name: "Gmail", - icon: GmailSvg, - activeIcon: GmailActiveSvg, - color: "#22C55E", - jumpList: { - data: [ - { - country_code: "mg", - ingress_country_code: "ru", - }, - { - country_code: "ru", - ingress_country_code: "fr", - }, - { - country_code: "fr", - ingress_country_code: "br", - }, - { - country_code: "br", - ingress_country_code: "us", - }, - ], - isLine: true, - name: "Gmail", - }, - }, - { - name: "Amazon", - icon: AmazonSvg, - activeIcon: AmazonActiveSvg, - color: "#EAB308", - jumpList: { - data: [ - { - country_code: "mg", - ingress_country_code: "ru", - }, - { - country_code: "ru", - ingress_country_code: "fr", - }, - { - country_code: "fr", - ingress_country_code: "br", - }, - { - country_code: "br", - ingress_country_code: "us", - }, - ], - isLine: true, - name: "Amazon", - }, - }, - { - name: "Ebay", - icon: EbaySvg, - activeIcon: EbayActiveSvg, - color: "#3B82F6", - jumpList: { - data: [ - { - country_code: "mg", - ingress_country_code: "ru", - }, - { - country_code: "ru", - ingress_country_code: "fr", - }, - { - country_code: "fr", - ingress_country_code: "br", - }, - { - country_code: "br", - ingress_country_code: "us", - }, - ], - isLine: true, - name: "Ebay", - }, - }, - { - name: "AppleNews", - icon: AppleNewsSvg, - activeIcon: AppleNewsActiveSvg, - color: "#EF4444", - jumpList: { - data: [ - { - country_code: "mg", - ingress_country_code: "ru", - }, - { - country_code: "ru", - ingress_country_code: "fr", - }, - { - country_code: "fr", - ingress_country_code: "br", - }, - { - country_code: "br", - ingress_country_code: "us", - }, - ], - isLine: true, - name: "AppleNews", - }, - }, - { - name: "CNN", - icon: CNNSvg, - activeIcon: CNNActiveSvg, - color: "#EF4444", - jumpList: { - data: [ - { - country_code: "mg", - ingress_country_code: "ru", - }, - { - country_code: "ru", - ingress_country_code: "fr", - }, - { - country_code: "fr", - ingress_country_code: "br", - }, - { - country_code: "br", - ingress_country_code: "us", - }, - ], - isLine: true, - name: "CNN", - }, - }, - { - name: "Browser", - icon: BrowserSvg, - activeIcon: BrowserActiveSvg, - color: "#8B5CF6", - jumpList: { - data: [ - { - country_code: "mg", - ingress_country_code: "ru", - }, - { - country_code: "ru", - ingress_country_code: "fr", - }, - { - country_code: "fr", - ingress_country_code: "br", - }, - { - country_code: "br", - ingress_country_code: "us", - }, - ], - isLine: true, - name: "Browser", - }, - }, - { - name: "YouTube", - icon: YouTubeSvg, - activeIcon: YouTubeActiveSvg, - color: "#EF4444", - jumpList: { - data: [ - { - country_code: "mg", - ingress_country_code: "ru", - }, - { - country_code: "ru", - ingress_country_code: "fr", - }, - { - country_code: "fr", - ingress_country_code: "br", - }, - { - country_code: "br", - ingress_country_code: "us", - }, - ], - isLine: true, - name: "YouTube", - }, - }, - { - name: "Facebook", - icon: FacebookSvg, - activeIcon: FacebookActiveSvg, - color: "#3B82F6", - jumpList: { - data: [ - { - country_code: "mg", - ingress_country_code: "ru", - }, - { - country_code: "ru", - ingress_country_code: "fr", - }, - { - country_code: "fr", - ingress_country_code: "br", - }, - { - country_code: "br", - ingress_country_code: "us", - }, - ], - isLine: true, - name: "Facebook", - }, - }, + { + name: "Netflix", + icon: NetflixSvg, + activeIcon: NetflixActiveSvg, + }, + { + name: "Spotify", + icon: SpotifySvg, + activeIcon: SpotifyActiveSvg, + }, + { + name: "Instagram", + icon: InstagramSvg, + activeIcon: InstagramActiveSvg, + }, + { + name: "Telegram", + icon: TelegramSvg, + activeIcon: TelegramActiveSvg, + }, + { + name: "Google", + icon: GoogleSvg, + activeIcon: GoogleActiveSvg, + }, + { + name: "Gmail", + icon: GmailSvg, + activeIcon: GmailActiveSvg, + }, + { + name: "Amazon", + icon: AmazonSvg, + activeIcon: AmazonActiveSvg, + }, + { + name: "Ebay", + icon: EbaySvg, + activeIcon: EbayActiveSvg, + }, + { + name: "AppleNews", + icon: AppleNewsSvg, + activeIcon: AppleNewsActiveSvg, + }, + { + name: "CNN", + icon: CNNSvg, + activeIcon: CNNActiveSvg, + }, + { + name: "Browser", + icon: BrowserSvg, + activeIcon: BrowserActiveSvg, + }, + { + name: "YouTube", + icon: YouTubeSvg, + activeIcon: YouTubeActiveSvg, + }, + { + name: "Facebook", + icon: FacebookSvg, + activeIcon: FacebookActiveSvg, + }, ]; export const CONST_TOOLTIP_TYPE = { - NESTED_ENCRYPTION: { - type: "NESTED_ENCRYPTION", - title: "嵌套加密", - }, - TRAFFIC_OBFUSCATION: { - type: "TRAFFIC_OBFUSCATION", - title: "流量混淆", - }, + NESTED_ENCRYPTION: { + type: "NESTED_ENCRYPTION", + title: "嵌套加密", + }, + TRAFFIC_OBFUSCATION: { + type: "TRAFFIC_OBFUSCATION", + title: "流量混淆", + }, + DYNAMIC_ROUTE_GENERATOR: { + type: "DYNAMIC_ROUTE_GENERATOR", + title: "动态路由生成", + }, + // 应用分流 + APP_DIVERSION: { + type: "APP_DIVERSION", + title: "应用分流", + }, + // 通行认证 + PASS_AUTHENTICATION: { + type: "PASS_AUTHENTICATION", + title: "通行认证", + }, }; const DecentralizedElasticNetwork = () => { - const { proxy_info, path_list } = useSelector( - (state: RootState) => state.web3Reducer - ); + const { proxy_info, path_list, newHomeProxies } = useSelector( + (state: RootState) => state.web3Reducer + ); - const [tooltipType, setTooltipType] = useState( - CONST_TOOLTIP_TYPE.NESTED_ENCRYPTION.type - ); - const [tooltipClosed, setTooltipClosed] = useState(false); + const [tooltipType, setTooltipType] = useState( + CONST_TOOLTIP_TYPE.PASS_AUTHENTICATION.type + ); + const [tooltipClosed, setTooltipClosed] = useState(false); - const [selectedApp, setSelectedApp] = useState(null); + const [selectedApp, setSelectedApp] = useState(null); + const appDiversion = useMemo(() => { + return Apps.map((item) => { + const findApp = APP_DIVERSION.find( + (appItem) => item.name === appItem.name + ); + return { + ...item, + ...findApp, + }; + }); + }, []); + const currentValue = useMemo(() => { + let value = null; + switch (tooltipType) { + case CONST_TOOLTIP_TYPE.NESTED_ENCRYPTION.type: + value = [NESTED_ENCRYPTION]; + break; + case CONST_TOOLTIP_TYPE.TRAFFIC_OBFUSCATION.type: + value = [TRAFFIC_OBFUSCATION]; + break; + case CONST_TOOLTIP_TYPE.DYNAMIC_ROUTE_GENERATOR.type: + value = DYNAMIC_ROUTE_GENERATOR + break; + case CONST_TOOLTIP_TYPE.APP_DIVERSION.type: + value = selectedApp ? [selectedApp] : [] + break; + case CONST_TOOLTIP_TYPE.PASS_AUTHENTICATION.type: + value = [PASS_AUTHENTICATION] + break; + default: + break; + } + return value; + }, [tooltipType,selectedApp]); - const handleClickApp = (item: any) => { - console.log("item", item); - setSelectedApp(item); - }; + const handleClickApp = (item: any) => { + console.log("item", item); + setSelectedApp(item); + }; - const screenData = useMemo(() => { - return { - path_list, - proxy_info, - }; - }, [path_list, proxy_info]); - return ( -
-
- {Apps.map((item) => { - return ( -
handleClickApp(item)} - > - {selectedApp?.name === item?.name ? ( - - ) : ( - - )} -
- ); - })} -
-
- -
-
-
{}}> - 通行认证 -
- -
{ - setTooltipType( - CONST_TOOLTIP_TYPE.NESTED_ENCRYPTION.type - ); - setTooltipClosed(true); - }} - > - 嵌套加密 -
-
{}}> - 动态路由生成 -
-
{ - setTooltipType( - CONST_TOOLTIP_TYPE.TRAFFIC_OBFUSCATION.type - ); - setTooltipClosed(true); - }} - > - 流量混淆 -
-
{}}> - 应用分流 -
+ useEffect(()=>{ + ()=>{ + setTooltipClosed(false); + } + },[]) + + return ( +
+
+ {tooltipType === CONST_TOOLTIP_TYPE.APP_DIVERSION.type && appDiversion.map((item) => { + return ( +
handleClickApp(item)} + > + {selectedApp?.name === item?.name ? ( + + ) : ( + + )}
+ ); + })} +
+
+ +
+
+
{ + setTooltipType(CONST_TOOLTIP_TYPE.PASS_AUTHENTICATION.type); + }} + > + 通行认证
- ); + +
{ + setTooltipType(CONST_TOOLTIP_TYPE.NESTED_ENCRYPTION.type); + setTooltipClosed(true); + }} + > + 嵌套加密 +
+
{ + setTooltipType(CONST_TOOLTIP_TYPE.DYNAMIC_ROUTE_GENERATOR.type); + }} + > + 动态路由生成 +
+
{ + setTooltipType(CONST_TOOLTIP_TYPE.TRAFFIC_OBFUSCATION.type); + setTooltipClosed(true); + }} + > + 流量混淆 +
+
{ + setTooltipType(CONST_TOOLTIP_TYPE.APP_DIVERSION.type); + }} + > + 应用分流 +
+
+
+ ); }; export default DecentralizedElasticNetwork; diff --git a/src/pages/new-home/components/world-geo.tsx b/src/pages/new-home/components/world-geo.tsx index af9d437..12b5c83 100644 --- a/src/pages/new-home/components/world-geo.tsx +++ b/src/pages/new-home/components/world-geo.tsx @@ -8,1128 +8,1233 @@ 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=="; + "image://data:image/svg+xml;charset=utf-8;base64,PHN2ZyB3aWR0aD0iNjciIGhlaWdodD0iMTAyIiB2aWV3Qm94PSIwIDAgNjcgMTAyIiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8ZyBmaWx0ZXI9InVybCgjZmlsdGVyMF9mXzYxMTdfMjEyNDA3KSI+CjxwYXRoIGQ9Ik0zNC4yMTA5IDkxLjE4ODZMNTMuNjU3OCA0MC45NThDNTQuOTM4IDM3LjY1MTMgNTUuNzk4MyAzNC4xNTkyIDU1LjM1NjMgMzAuNjQxQzU0LjQzNTcgMjMuMzEyOCA1MC40Njg0IDExLjAyMDggMzQuMjExMiAxMS4wMjA4QzE5LjE5MDMgMTEuMDIwOCAxMy45MTEgMjEuNTE0NiAxMi4wNTU0IDI4Ljg5MTJDMTAuOTAxIDMzLjQ4MDYgMTEuOTkyNiAzOC4yMTg2IDEzLjgyMzEgNDIuNTgyN0wzNC4yMTA5IDkxLjE4ODZaIiBmaWxsPSJ1cmwoI3BhaW50MF9saW5lYXJfNjExN18yMTI0MDcpIi8+CjwvZz4KPGRlZnM+CjxmaWx0ZXIgaWQ9ImZpbHRlcjBfZl82MTE3XzIxMjQwNyIgeD0iMC44OTE3NDQiIHk9IjAuMzMxOTkiIHdpZHRoPSI2NS4yNzA3IiBoZWlnaHQ9IjEwMS41NDUiIGZpbHRlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIj4KPGZlRmxvb2QgZmxvb2Qtb3BhY2l0eT0iMCIgcmVzdWx0PSJCYWNrZ3JvdW5kSW1hZ2VGaXgiLz4KPGZlQmxlbmQgbW9kZT0ibm9ybWFsIiBpbj0iU291cmNlR3JhcGhpYyIgaW4yPSJCYWNrZ3JvdW5kSW1hZ2VGaXgiIHJlc3VsdD0ic2hhcGUiLz4KPGZlR2F1c3NpYW5CbHVyIHN0ZERldmlhdGlvbj0iNS4zNDQ0MSIgcmVzdWx0PSJlZmZlY3QxX2ZvcmVncm91bmRCbHVyXzYxMTdfMjEyNDA3Ii8+CjwvZmlsdGVyPgo8bGluZWFyR3JhZGllbnQgaWQ9InBhaW50MF9saW5lYXJfNjExN18yMTI0MDciIHgxPSIzNS4yODI2IiB5MT0iMTAuODU2NCIgeDI9IjM1LjI4MjYiIHkyPSI4Ni44NTY0IiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CjxzdG9wIHN0b3AtY29sb3I9IiMwMEYyRkYiLz4KPHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjMTUwMEZGIi8+CjwvbGluZWFyR3JhZGllbnQ+CjwvZGVmcz4KPC9zdmc+Cg=="; interface LinesItemType { - name: string; - country_code: string; - value: number[]; + name: string; + country_code: string; + value: number[]; } type LinesDataType = [LinesItemType, LinesItemType]; type LinesType = [string, LinesDataType[]]; // 创建单个国家的涟漪效果 const createCountryRipple = (countryCode: string) => { - const coords = geoCoordMap[countryCode]; - if (!coords) return null; - return { - name: countryCodeMap[countryCode] ?? "", - value: coords, - country_code: countryCode, - }; + const coords = geoCoordMap[countryCode]; + if (!coords) return null; + return { + name: countryCodeMap[countryCode] ?? "", + value: coords, + country_code: countryCode, + }; }; export const WorldGeo = memo( - ({ - newHomeProxies, - selectedApp, - tooltipType, - tooltipClosed, - setTooltipClosed, - }: { - newHomeProxies: any; - selectedApp: any; - tooltipType: string; - tooltipClosed: boolean; - setTooltipClosed: (value: boolean) => void; - }) => { - // const queryClient = useQueryClient() - // 嵌套加密ref - const customTooltipRef = useRef(null); - // 流量混淆ref - const customTooltip2Ref = useRef(null); - const proxyGeoRef = useRef(null); - const preMainToData = useRef<{ country_code: string }[]>([]); - const lineMidpointsRef = useRef< - { - id: string; - midpoint: number[]; - fromCountry: string; - toCountry: string; - }[] - >([]); - const labelContainerRef = useRef(null); - const labelsRef = useRef([]); - - const mainToData = useMemo(() => { - // 使用新的数据结构 - const proxiesList = - selectedApp && selectedApp?.jumpList - ? [selectedApp.jumpList] - : newHomeProxies ?? []; - // 初始化数据数组 - 不再包含 startCountry - const data: any = []; - // 遍历代理列表 - proxiesList.forEach((proxyItem: any) => { - // 检查是否有数据数组 - if (proxyItem.data && Array.isArray(proxyItem.data)) { - // 遍历数据数组中的每个项目 - proxyItem.data.forEach((item: any) => { - // 如果有 ingress_country_code,则添加一对起点和终点 - if (item.ingress_country_code) { - // 添加起点(country_code) - data.push({ - country_code: item.country_code, - type: "start", - isLine: proxyItem.isLine, // 保存连线标志 - }); - // 添加终点(ingress_country_code) - data.push({ - country_code: item.ingress_country_code, - type: "end", - isLine: proxyItem.isLine, // 保存连线标志 - }); - } else { - // 如果没有 ingress_country_code,只添加 country_code - data.push({ - country_code: item.country_code, - isLine: proxyItem.isLine, // 保存连线标志 - }); - } - }); - } - }); - return data; - }, [newHomeProxies, selectedApp]); - - // 创建自定义提示框DOM元素 - const createCustomTooltip = () => { - // 如果已经存在自定义提示框,则移除它 - if (document.getElementById("custom-fixed-tooltip")) { - document.getElementById("custom-fixed-tooltip")?.remove(); + ({ + newHomeProxies, + selectedApp, + tooltipType, + tooltipClosed, + setTooltipClosed, + }: { + newHomeProxies: any; + selectedApp: any; + tooltipType: string; + tooltipClosed: boolean; + setTooltipClosed: (value: boolean) => void; + }) => { + // const queryClient = useQueryClient() + // 嵌套加密ref + const customTooltipRef = useRef(null); + // 流量混淆ref + const customTooltip2Ref = useRef(null); + const proxyGeoRef = useRef(null); + const preMainToData = useRef<{ country_code: string }[]>([]); + const lineMidpointsRef = useRef< + { + id: string; + midpoint: number[]; + fromCountry: string; + toCountry: string; + }[] + >([]); + const labelContainerRef = useRef(null); + const labelsRef = useRef([]); + const mainToData = useMemo(() => { + // 使用新的数据结构 + const proxiesList = + selectedApp && selectedApp ? [...newHomeProxies,selectedApp] : newHomeProxies ?? []; + // 初始化数据数组 - 不再包含 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, // 保存颜色信息 + }); + // 添加终点(ingress_country_code) + data.push({ + country_code: item.ingress_country_code, + type: "end", + isLine: proxyItem.isLine, // 保存连线标志 + color: proxyItem.color, // 保存颜色信息 + }); + } else { + // 如果没有 ingress_country_code,只添加 country_code + data.push({ + country_code: item.country_code, + isLine: proxyItem.isLine, // 保存连线标志 + color: proxyItem.color, // 保存颜色信息 + }); } - // 创建自定义提示框 - const tooltip = document.createElement("div"); - tooltip.id = "custom-fixed-tooltip"; - tooltip.style.position = "fixed"; - tooltip.style.zIndex = "1000"; - tooltip.style.pointerEvents = "auto"; - tooltip.style.backgroundColor = "transparent"; - // 设置提示框内容 - const currentTooltipType = - CONST_TOOLTIP_TYPE[ - tooltipType as keyof typeof CONST_TOOLTIP_TYPE - ] || CONST_TOOLTIP_TYPE.NESTED_ENCRYPTION; - tooltip.innerHTML = ` + }); + } + }); + return data; + }, [newHomeProxies, selectedApp]); + // 创建自定义提示框DOM元素 + const createCustomTooltip = () => { + // 如果已经存在自定义提示框,则移除它 + if (document.getElementById("custom-fixed-tooltip")) { + document.getElementById("custom-fixed-tooltip")?.remove(); + } + // 创建自定义提示框 + const tooltip = document.createElement("div"); + tooltip.id = "custom-fixed-tooltip"; + tooltip.style.position = "fixed"; + tooltip.style.zIndex = "1000"; + tooltip.style.pointerEvents = "auto"; + tooltip.style.backgroundColor = "transparent"; + // 设置提示框内容 + const currentTooltipType = + CONST_TOOLTIP_TYPE[tooltipType as keyof typeof CONST_TOOLTIP_TYPE] || + CONST_TOOLTIP_TYPE.NESTED_ENCRYPTION; + tooltip.innerHTML = `
${ - currentTooltipType.title + currentTooltipType.title }
- - + CONST_TOOLTIP_TYPE.NESTED_ENCRYPTION.type === currentTooltipType.type + ? "image/nested-encryption.png" + : "image/traffic-obfuscation.png" + )}" alt="" />
`; - // 添加到DOM - document.body.appendChild(tooltip); - customTooltipRef.current = tooltip; - // 添加关闭按钮事件 - const closeButton = tooltip.querySelector(".close-icon"); - if (closeButton) { - closeButton.addEventListener("click", () => { - setTooltipClosed(false); - tooltip.remove(); - customTooltipRef.current = null; - }); - } - // 定位提示框 - positionCustomTooltip(); - }; - // 定位自定义提示框 - 优化版本 - const positionCustomTooltip = () => { - if (!customTooltipRef.current || !proxyGeoRef.current) return; - // 找到US点 - const coords = geoCoordMap["GL"]; - if (!coords) return; - try { - // 将地理坐标转换为屏幕坐标 - const screenCoord = proxyGeoRef.current.convertToPixel( - "geo", - coords - ); - if ( - screenCoord && - Array.isArray(screenCoord) && - screenCoord.length === 2 - ) { - // 设置提示框位置 - customTooltipRef.current.style.left = `${ - screenCoord[0] + 232 + 7 - }px`; - customTooltipRef.current.style.top = `${ - screenCoord[1] + 40 - 190 - }px`; - } - } catch (error) { - console.error("Error positioning tooltip:", error); - } - }; - // 主线每个节点tip竖线的经纬度,修改tip 竖线的高度也可以用这个 - const mianLineData = (data: typeof mainToData) => { - return ( - data - .map((item: any) => { - const countryCode = item.country_code.toUpperCase(); - - if ( - !( - ["RU", "FR"].includes(countryCode) && - item.type === "start" - ) - ) - return null; - const coords = geoCoordMap[countryCode] as - | [number, number] - | undefined; - if (!coords) return null; - return { - name: countryCodeMap[countryCode], - coords: [coords, [coords[0], coords[1] + 4]], - value: countryCode, - }; - }) - .filter((v: any) => !!v) ?? [] - ); - }; - const getLineItem = ( - preCode: string, - nextCode: string - ): [LinesItemType, LinesItemType] => { - return [ - { - name: countryCodeMap[preCode] ?? "", - value: geoCoordMap[preCode] ?? [], - country_code: preCode, - }, - { - name: countryCodeMap[nextCode] ?? "", - value: geoCoordMap[nextCode] ?? [], - country_code: nextCode, - }, - ]; - }; - const getLine = () => { - // 实现数据处理 - const solidData: LinesType[] = [["main", []]]; // 使用"main"替代startCountry.country_code - // 收集需要显示涟漪效果的所有点(包括连线和不连线的) - const ripplePoints: any[] = []; - // 处理主路径数据 - for (let i = 0; i < mainToData.length; i++) { - // 如果是最后一个元素,则跳过(因为没有下一个元素作为终点) - if (i === mainToData.length - 1) continue; - const currentItem = mainToData[i]; - const nextItem = mainToData[i + 1]; - // 获取当前国家代码 - const countryCode = currentItem.country_code.toUpperCase(); - // 如果当前项是起点,下一项是终点 - if (currentItem.type === "start" && nextItem.type === "end") { - const startCode = countryCode; - const endCode = nextItem.country_code.toUpperCase(); - // 无论是否连线,都添加点的涟漪效果 - const startPoint = createCountryRipple(startCode); - const endPoint = createCountryRipple(endCode); - if (startPoint) ripplePoints.push(startPoint); - if (endPoint) ripplePoints.push(endPoint); - // 检查是否应该绘制连线 - if (currentItem.isLine !== false) { - solidData[0]?.[1].push(getLineItem(startCode, endCode)); - } - // 跳过下一项,因为已经处理了 - i++; - } - // 常规情况:当前项到下一项 - else { - const nextCountryCode = nextItem.country_code.toUpperCase(); - // 无论是否连线,都添加点的涟漪效果 - const currentPoint = createCountryRipple(countryCode); - const nextPoint = createCountryRipple(nextCountryCode); - if (currentPoint) ripplePoints.push(currentPoint); - if (nextPoint) ripplePoints.push(nextPoint); - // 检查是否应该绘制连线 - if (currentItem.isLine !== false) { - solidData[0]?.[1].push( - getLineItem(countryCode, nextCountryCode) - ); - } - } - } - // 虚线数据处理(保持原有逻辑) - const otherLineList :any = []; + // 添加到DOM + document.body.appendChild(tooltip); + customTooltipRef.current = tooltip; + // 添加关闭按钮事件 + const closeButton = tooltip.querySelector(".close-icon"); + if (closeButton) { + closeButton.addEventListener("click", () => { + setTooltipClosed(false); + tooltip.remove(); + customTooltipRef.current = null; + }); + } + // 定位提示框 + positionCustomTooltip(); + }; + // 定位自定义提示框 - 优化版本 + const positionCustomTooltip = () => { + if (!customTooltipRef.current || !proxyGeoRef.current) return; + // 找到US点 + const coords = geoCoordMap["GL"]; + if (!coords) return; + try { + // 将地理坐标转换为屏幕坐标 + const screenCoord = proxyGeoRef.current.convertToPixel("geo", coords); + if ( + screenCoord && + Array.isArray(screenCoord) && + screenCoord.length === 2 + ) { + // 设置提示框位置 + customTooltipRef.current.style.left = `${screenCoord[0] + 232 + 7}px`; + customTooltipRef.current.style.top = `${screenCoord[1] + 40 - 190}px`; + } + } catch (error) { + console.error("Error positioning tooltip:", error); + } + }; + // 创建自定义提示框DOM元素 + const createCustomTooltip2 = () => { + // 如果已经存在自定义提示框,则移除它 + if (document.getElementById("custom-fixed-tooltip2")) { + document.getElementById("custom-fixed-tooltip2")?.remove(); + } + // 创建自定义提示框 + const tooltip = document.createElement("div"); + tooltip.id = "custom-fixed-tooltip2"; + tooltip.style.position = "fixed"; + tooltip.style.zIndex = "1000"; + tooltip.style.pointerEvents = "auto"; + tooltip.style.backgroundColor = "transparent"; + tooltip.innerHTML = ` +
+
+
+
+
流量混淆
+ +
+ +
+ +
+ `; + // 添加到DOM + document.body.appendChild(tooltip); + customTooltip2Ref.current = tooltip; + // 添加关闭按钮事件 + const closeButton = tooltip.querySelector(".close-icon"); + if (closeButton) { + closeButton.addEventListener("click", () => { + setTooltipClosed(false); + tooltip.remove(); + customTooltip2Ref.current = null; + }); + } + // 定位提示框 + positionCustomTooltip2(); + }; + // 定位自定义提示框 - 优化版本 + const positionCustomTooltip2 = () => { + if (!customTooltip2Ref.current || !proxyGeoRef.current) return; + // 找到US点 + const coords = geoCoordMap["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); + } + }; + // 主线每个节点tip竖线的经纬度,修改tip 竖线的高度也可以用这个 + const mianLineData = (data: typeof mainToData) => { + return ( + data + .map((item: any) => { + const countryCode = item.country_code.toUpperCase(); + if (!(["RU", "FR"].includes(countryCode) && item.type === "start")) + return null; + const coords = geoCoordMap[countryCode] as + | [number, number] + | undefined; + if (!coords) return null; return { - solidData, - otherLineList, - ripplePoints, + name: countryCodeMap[countryCode], + coords: [coords, [coords[0], coords[1] + 4]], + value: countryCode, }; - }; - // 获取连线经纬度数据 - const convertData = (data: LinesDataType[]) => { - const res = []; - const midpoints = []; - - for (let index = 0; index < data.length; index++) { - const dataIndex = data[index]; - const fromCoord = - geoCoordMap[dataIndex?.[0]?.country_code ?? ""]; - const toCoord = geoCoordMap[dataIndex?.[1]?.country_code ?? ""]; - const fromCountry = dataIndex?.[0]?.country_code ?? ""; - const toCountry = dataIndex?.[1]?.country_code ?? ""; - - if (fromCoord && toCoord) { - res.push([fromCoord, toCoord]); - - // 计算中点,考虑曲线的弧度 - const curveness = -0.4; // 与飞线弧度相同 - const x1 = fromCoord[0]; - const y1 = fromCoord[1]; - const x2 = toCoord[0]; - const y2 = toCoord[1]; - - // 计算控制点 - const cpx = (x1 + x2) / 2 - (y2 - y1) * curveness; - const cpy = (y1 + y2) / 2 - (x1 - x2) * curveness; - - // 计算曲线上的中点 (t=0.5 时的贝塞尔曲线点) - const midX = x1 * 0.25 + cpx * 0.5 + x2 * 0.25; - const midY = y1 * 0.25 + cpy * 0.5 + y2 * 0.25; - - midpoints.push({ - id: `line-label-${index}`, - midpoint: [midX, midY], - fromCountry, - toCountry, - }); - } + }) + .filter((v: any) => !!v) ?? [] + ); + }; + const getLineItem = ( + preCode: string, + nextCode: string + ): [LinesItemType, LinesItemType] => { + return [ + { + name: countryCodeMap[preCode] ?? "", + value: geoCoordMap[preCode] ?? [], + country_code: preCode, + }, + { + name: countryCodeMap[nextCode] ?? "", + value: geoCoordMap[nextCode] ?? [], + country_code: nextCode, + }, + ]; + }; + const getLine = () => { + // 实现数据处理 + const solidData: LinesType[] = [["main", []]]; // 使用"main"替代startCountry.country_code + // 收集需要显示涟漪效果的所有点(包括连线和不连线的) + const ripplePoints: any[] = []; + // 收集每个点的颜色信息 + const pointColors: Record = {}; + + // 处理主路径数据 + 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 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); + + if (startPoint) { + ripplePoints.push({ + ...startPoint, + color: lineColor // 添加颜色信息 + }); + } + + if (endPoint) { + ripplePoints.push({ + ...endPoint, + color: lineColor // 添加颜色信息 + }); + } + + // 检查是否应该绘制连线 + if (currentItem.isLine !== false) { + const lineItem = getLineItem(startCode, endCode); + // 添加颜色信息到连线数据 + solidData[0][1].push({ + ...lineItem, + color: lineColor // 添加颜色信息 + } as any); + } + // 跳过下一项,因为已经处理了 + i++; + } + // 常规情况:当前项到下一项 + else { + const nextCountryCode = nextItem.country_code.toUpperCase(); + + // 保存下一个点的颜色信息 + pointColors[nextCountryCode] = nextItem.color || lineColor; + + // 无论是否连线,都添加点的涟漪效果 + const currentPoint = createCountryRipple(countryCode); + const nextPoint = createCountryRipple(nextCountryCode); + + if (currentPoint) { + ripplePoints.push({ + ...currentPoint, + color: lineColor // 添加颜色信息 + }); + } + + if (nextPoint) { + ripplePoints.push({ + ...nextPoint, + color: nextItem.color || lineColor // 添加颜色信息 + }); + } + + // 检查是否应该绘制连线 + if (currentItem.isLine !== false) { + const lineItem = getLineItem(countryCode, nextCountryCode); + // 添加颜色信息到连线数据 + solidData[0][1].push({ + ...lineItem, + color: lineColor // 添加颜色信息 + } as any); + } + } + } + // 虚线数据处理(保持原有逻辑) + const otherLineList: any = []; + return { + solidData, + otherLineList, + ripplePoints, + pointColors + }; + }; + // 获取连线经纬度数据 + const convertData = (data: LinesDataType[]) => { + const res = []; + const midpoints = []; + for (let index = 0; index < data.length; index++) { + const dataIndex = data[index]; + const fromCoord = geoCoordMap[dataIndex?.[0]?.country_code ?? ""]; + const toCoord = geoCoordMap[dataIndex?.[1]?.country_code ?? ""]; + const fromCountry = dataIndex?.[0]?.country_code ?? ""; + const toCountry = dataIndex?.[1]?.country_code ?? ""; + const lineColor = (dataIndex as any)?.color || "#0ea5e9"; // 获取线条颜色 + + if (fromCoord && toCoord) { + res.push({ + coords: [fromCoord, toCoord], + lineStyle: { + color: lineColor // 使用自定义颜色 } - - // 更新中点引用 - // lineMidpointsRef.current = midpoints; - - return res; - }; - // 创建双层点效果 - 大点 - const createDualLayerPoint = ( - lastExit: LinesItemType, - isMainPath: boolean = true - ) => { - // 创建数据数组,用于两个散点图层 - const pointData = lastExit - ? [lastExit].map((v) => { - return { - name: v.name, - value: v.value, - datas: { - country_code: v.country_code, - }, - }; - }) - : []; - // 根据是否是主路径设置不同的大小和颜色 - const outerSize = isMainPath ? 8 : 4; - const innerSize = isMainPath ? 4 : 2; - // Use selectedApp.color if available, otherwise default to blue - const outerColor = selectedApp?.color || "#0ea5e9"; // Use selectedApp.color with fallback - const innerColor = "#FFFFFF"; // 白色内层 - return [ - { - // 外层蓝色点,带涟漪效果 - type: "effectScatter", - coordinateSystem: "geo", - zlevel: 3, - color: outerColor, - symbol: "circle", - symbolSize: outerSize, - rippleEffect: { - period: 8, // 动画时间,值越小速度越快 - brushType: "stroke", // 波纹绘制方式 stroke - scale: 6, // 波纹圆环最大限制,值越大波纹越大 - brushWidth: 2, - }, - label: { - show: false, - }, - tooltip: { - show: false, - trigger: "item", - showContent: true, - alwaysShowContent: true, - formatter: (params: any) => { - // const countryCode = params.data.datas.country_code; - // const countryName = params.data.name; - // 创建自定义HTML提示框 - return ` + }); + // 计算中点,考虑曲线的弧度 + const curveness = -0.4; // 与飞线弧度相同 + const x1 = fromCoord[0]; + const y1 = fromCoord[1]; + const x2 = toCoord[0]; + const y2 = toCoord[1]; + // 计算控制点 + const cpx = (x1 + x2) / 2 - (y2 - y1) * curveness; + const cpy = (y1 + y2) / 2 - (x1 - x2) * curveness; + // 计算曲线上的中点 (t=0.5 时的贝塞尔曲线点) + const midX = x1 * 0.25 + cpx * 0.5 + x2 * 0.25; + const midY = y1 * 0.25 + cpy * 0.5 + y2 * 0.25; + midpoints.push({ + id: `line-label-${index}`, + midpoint: [midX, midY], + fromCountry, + toCountry, + }); + } + } + // 更新中点引用 + // lineMidpointsRef.current = midpoints; + return res; + }; + // 创建双层点效果 - 大点 + const createDualLayerPoint = ( + lastExit: LinesItemType, + isMainPath: boolean = true, + color?: string + ) => { + // 创建数据数组,用于两个散点图层 + const pointData = lastExit + ? [lastExit].map((v) => { + return { + name: v.name, + value: v.value, + datas: { + country_code: v.country_code, + }, + }; + }) + : []; + // 根据是否是主路径设置不同的大小和颜色 + 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, + symbol: "circle", + symbolSize: outerSize, + rippleEffect: { + period: 8, // 动画时间,值越小速度越快 + brushType: "stroke", // 波纹绘制方式 stroke + scale: 6, // 波纹圆环最大限制,值越大波纹越大 + brushWidth: 2, + }, + label: { + show: false, + }, + tooltip: { + show: false, + trigger: "item", + showContent: true, + alwaysShowContent: true, + formatter: (params: any) => { + // const countryCode = params.data.datas.country_code; + // const countryName = params.data.name; + // 创建自定义HTML提示框 + return `
嵌套加密
`; - }, - backgroundColor: "transparent", - borderWidth: 0, - }, - data: pointData, - } as echarts.SeriesOption, - { - // 内层白色点,不带涟漪效果 - type: "scatter", // 使用普通scatter,不带特效 - coordinateSystem: "geo", - zlevel: 4, // 确保在蓝色点上方 - color: innerColor, - symbol: "circle", - symbolSize: innerSize, - label: { - show: false, - }, - data: pointData, - } as echarts.SeriesOption, - ]; + }, + backgroundColor: "transparent", + borderWidth: 0, + }, + data: pointData, + } as echarts.SeriesOption, + { + // 内层白色点,不带涟漪效果 + type: "scatter", // 使用普通scatter,不带特效 + coordinateSystem: "geo", + zlevel: 4, // 确保在蓝色点上方 + color: innerColor, + symbol: "circle", + symbolSize: innerSize, + label: { + show: false, + }, + data: pointData, + } as echarts.SeriesOption, + ]; + }; + // 添加新方法:根据经纬度数组创建蓝色涟漪小点(不包含白色内层点) + const createRipplePointsFromCoordinates = ( + coordinates: [number, number][], + series: echarts.SeriesOption[] + ) => { + if (!coordinates || coordinates.length === 0) return; + // 使用selectedApp.color或默认蓝色 + const outerColor = "#01FF5E"; + // 只创建外层带涟漪效果的点 + series.push({ + type: "effectScatter", + coordinateSystem: "geo", + zlevel: 3, + color: outerColor, + symbol: "circle", + symbolSize: 6, + rippleEffect: { + period: 8, // 动画时间 + brushType: "stroke", // 波纹绘制方式 + scale: 6, // 波纹圆环最大限制 + brushWidth: 2, + }, + label: { + show: false, + }, + data: coordinates.map((coord) => ({ + name: "", // 可以根据需要添加名称 + value: coord, + })), + } as echarts.SeriesOption); + }; + // 创建路径点的双层效果 + const createPathPoints = ( + dataItems: LinesDataType[], + isMainPath: boolean = true + ) => { + // 创建数据数组 + const pointData = dataItems.map((dataItem: LinesDataType) => { + const color = (dataItem as any).color || "#0ea5e9"; // 获取颜色信息 + return { + name: dataItem[0].name, + value: geoCoordMap[dataItem[0].country_code], + datas: { + country_code: dataItem[0].country_code, + color: color // 保存颜色信息 + }, }; - // 创建路径点的双层效果 - const createPathPoints = ( - dataItems: LinesDataType[], - isMainPath: boolean = true - ) => { - // 创建数据数组 - const pointData = dataItems.map((dataItem: LinesDataType) => { - return { - name: dataItem[0].name, - value: geoCoordMap[dataItem[0].country_code], - datas: { - country_code: dataItem[0].country_code, - }, - }; - }); - // 根据是否是主路径设置不同的大小和颜色 - const outerSize = isMainPath ? 8 : 4; - const innerSize = isMainPath ? 4 : 2; - // Use selectedApp.color if available, otherwise default to blue - const outerColor = selectedApp?.color || "#0ea5e9"; // Use selectedApp.color with fallback - const innerColor = "#FFFFFF"; // 白色内层 - return [ - { - // 外层蓝色点,带涟漪效果 - type: "effectScatter", - coordinateSystem: "geo", - zlevel: 3, - color: outerColor, - symbol: "circle", - symbolSize: outerSize, - rippleEffect: { - period: 8, // 动画时间,值越小速度越快 - brushType: "stroke", // 波纹绘制方式 stroke - scale: 6, // 波纹圆环最大限制,值越大波纹越大 - brushWidth: 2, - }, - label: { - show: false, - }, - tooltip: { - show: false, - trigger: "item", - formatter: (params: any) => { - // const countryCode = params.data.datas.country_code; - // const countryName = params.data.name; - // 创建自定义HTML提示框 - return ` + }); + // 根据是否是主路径设置不同的大小和颜色 + const outerSize = isMainPath ? 8 : 4; + const innerSize = isMainPath ? 4 : 2; + const innerColor = "#FFFFFF"; // 白色内层 + return [ + { + // 外层彩色点,带涟漪效果 + type: "effectScatter", + coordinateSystem: "geo", + zlevel: 3, + // 使用回调函数根据数据项设置颜色 + itemStyle: { + color: function(params: any) { + return params.data.datas.color || "#0ea5e9"; + } + }, + symbol: "circle", + symbolSize: outerSize, + rippleEffect: { + period: 8, // 动画时间,值越小速度越快 + brushType: "stroke", // 波纹绘制方式 stroke + scale: 6, // 波纹圆环最大限制,值越大波纹越大 + brushWidth: 2, + }, + label: { + show: false, + }, + tooltip: { + show: false, + trigger: "item", + formatter: (params: any) => { + return `
嵌套加密
`; - }, - backgroundColor: "transparent", - borderWidth: 0, - }, - data: pointData, - } as echarts.SeriesOption, - { - // 内层白色点,不带涟漪效果 - type: "scatter", // 使用普通scatter,不带特效 - coordinateSystem: "geo", - zlevel: 4, // 确保在蓝色点上方 - color: innerColor, - symbol: "circle", - symbolSize: innerSize, - label: { - show: false, - }, - data: pointData, - } as echarts.SeriesOption, - ]; - }; - // 创建带自定义提示框的涟漪点 - const createRipplePointsWithTooltip = (ripplePoints: any) => { - // Use selectedApp.color if available, otherwise default to blue - const outerColor = selectedApp?.color || "#0ea5e9"; // Use selectedApp.color with fallback - return { - type: "effectScatter", - coordinateSystem: "geo", - zlevel: 3, - color: outerColor, - symbol: "circle", - symbolSize: 8, - rippleEffect: { - period: 8, - brushType: "stroke", - scale: 6, - brushWidth: 2, - }, - label: { - show: false, - formatter: (params: any) => { - console.log(params, "params"); - return `{${params.data.datas.country_code}|}`; - }, - }, - // 添加提示框配置 - tooltip: { - show: false, - trigger: "item", - formatter: (params: any) => { - // const countryCode = params.data.datas.country_code; - // const countryName = params.data.name; - // 创建自定义HTML提示框 - return ` + }, + backgroundColor: "transparent", + borderWidth: 0, + }, + data: pointData, + } as echarts.SeriesOption, + { + // 内层白色点,不带涟漪效果 + type: "scatter", // 使用普通scatter,不带特效 + coordinateSystem: "geo", + zlevel: 4, // 确保在蓝色点上方 + color: innerColor, + symbol: "circle", + symbolSize: innerSize, + label: { + show: false, + }, + data: pointData, + } as echarts.SeriesOption, + ]; + }; + // 创建带自定义提示框的涟漪点 + const createRipplePointsWithTooltip = (ripplePoints: any) => { + return { + type: "effectScatter", + coordinateSystem: "geo", + zlevel: 3, + // 使用回调函数根据数据项设置颜色 + itemStyle: { + color: function(params: any) { + return params.data.datas.color || "#0ea5e9"; + } + }, + symbol: "circle", + symbolSize: 8, + rippleEffect: { + period: 8, + brushType: "stroke", + scale: 6, + brushWidth: 2, + }, + label: { + show: false, + formatter: (params: any) => { + console.log(params, "params"); + return `{${params.data.datas.country_code}|}`; + }, + }, + // 添加提示框配置 + tooltip: { + show: false, + trigger: "item", + formatter: (params: any) => { + return `
嵌套加密
`; - }, - backgroundColor: "transparent", - borderWidth: 0, - }, - data: ripplePoints.map((point: any) => ({ - name: point.name, - value: point.value, - datas: { - country_code: point.country_code, - }, - })), - } as echarts.SeriesOption; - }; - // 连线 series - const getLianData = (series: echarts.SeriesOption[]) => { - const { solidData, otherLineList, ripplePoints } = getLine(); - // 如果有需要显示涟漪效果的点,添加它们 - if (ripplePoints.length > 0) { - // 添加带自定义提示框的外层蓝色点 - series.push(createRipplePointsWithTooltip(ripplePoints)); - // 添加内层白色点,不带涟漪效果 - series.push({ - type: "scatter", // 使用普通scatter,不带特效 - coordinateSystem: "geo", - zlevel: 4, // 确保在蓝色点上方 - color: "#FFFFFF", // 白色内层 - symbol: "circle", - symbolSize: 4, - label: { - show: false, - }, - data: ripplePoints.map((point) => ({ - name: point.name, - value: point.value, - datas: { - country_code: point.country_code, - }, - })), - } as echarts.SeriesOption); - } - solidData.forEach((item) => { - // 如果没有连线数据,则跳过 - if (item[1].length === 0) { - return; - } - const lastExit = item[1]?.[item[1].length - 1]?.[1] ?? null; - // 添加飞行线 - series.push({ - name: item[0], - type: "lines", - zlevel: 1, - label: { - show: false, // 不使用内置标签 - }, - // 飞行线特效 - effect: { - show: true, // 是否显示 - period: 4, // 特效动画时间 - trailLength: 0.7, // 特效尾迹长度。取从 0 到 1 的值,数值越大尾迹越长 - symbol: planePathImg, // 特效图形标记 - symbolSize: [10, 20], - }, - // 线条样式 - lineStyle: { - curveness: -0.4, // 飞线弧度 - type: "solid", // 飞线类型 - color: selectedApp?.color || "#0ea5e9", // Use selectedApp.color with fallback - width: 1.5, // 飞线宽度 - opacity: 0.1, - }, - data: convertData( - item[1] - ) as echarts.LinesSeriesOption["data"], - }); - // 添加路径点的双层效果 - const pathPoints = createPathPoints(item[1], true); - series.push(...pathPoints); - // 添加出口节点的双层效果 - if (lastExit) { - const exitNodes = createDualLayerPoint(lastExit, true); - series.push(...exitNodes); - } - }); - otherLineList.forEach((line: any) => { - line.forEach((item: any) => { - const lastExit = item[1]?.[item[1].length - 1]?.[1] ?? null; - // 添加虚线 - series.push({ - name: item[0], - type: "lines", - zlevel: 1, - label: { - show: false, - }, - // 线条样式 - lineStyle: { - curveness: -0.4, // 飞线弧度 - type: [5, 5], // 飞线类型 - color: "#F0FFA2", // 飞线颜色 - width: 0.5, // 飞线宽度 - opacity: 0.6, - }, - data: convertData( - item[1] - ) as echarts.LinesSeriesOption["data"], - }); - // 添加路径点的双层效果(次要路径) - const pathPoints = createPathPoints(item[1], false); - series.push(...pathPoints); - // 添加出口节点的双层效果(次要路径) - if (lastExit) { - const exitNodes = createDualLayerPoint(lastExit, false); - series.push(...exitNodes); - } - }); - }); - return true; - }; - // 主线tip series - const getMianLineTipData = (series: echarts.SeriesOption[] = []) => { - series.push( - // 柱状体的主干 - { - name: "solidTip", - type: "lines", - zlevel: 5, - effect: { - show: false, - symbolSize: 5, // 图标大小 - }, - lineStyle: { - width: 0, // 尾迹线条宽度 - color: "#F0FFA2", - opacity: 1, // 尾迹线条透明度 - curveness: 0, // 尾迹线条曲直度 - }, - label: { - show: true, - position: "end", - color: "#fff", - formatter: (parameters) => { - const isFr = parameters.value === "FR"; - console.log(parameters, "parameters"); - const name = isFr ? "权威节点团" : "待认证节点"; - return `{left|}{gap2|}{name${ - isFr ? "2" : "1" - }|${name}}{gap3|}{right|}`; - }, - rich: { - // left: { - // color: "transparent", - // height: 35, - // width: 8, - // align: "center", - // backgroundColor: { - // image: `data:image/svg+xml;charset=utf-8;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMSIgaGVpZ2h0PSI1OCIgdmlld0JveD0iMCAwIDExIDU4IiBmaWxsPSJub25lIj4KPHBhdGggZD0iTTExIDU2LjA4ODRIMVY0Ni4wODg0IiBzdHJva2U9IiNGRkZERDMiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIi8+CjxwYXRoIGQ9Ik0xIDExVjFIMTEiIHN0cm9rZT0iI0ZGRkREMyIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiLz4KPHBhdGggZD0iTTguNzI5NDkgMTlWMzkiIHN0cm9rZT0iI0ZGRkREMyIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiLz4KPC9zdmc+`, // 动态生成国旗图标 - // }, - // }, - gap1: { - height: 35, - width: 8, - }, - - gap2: { - height: 35, - width: 6, - }, - name1: { - color: "#FF6B01", - align: "center", - lineHeight: 35, - fontSize: 18, - fontWeight: 600, - padding: [11, 16.52, 11, 16.52], - backgroundColor: "rgba(63, 6, 3, 0.5)", - }, - name2: { - color: "#37FF00", - align: "center", - lineHeight: 35, - fontSize: 18, - fontWeight: 600, - padding: [11, 16.52, 11, 16.52], - backgroundColor: "rgba(4, 59, 27, 0.5)", - }, - gap3: { - height: 35, - width: 8, - }, - // right: { - // color: "transparent", - // height: 35, - // width: 8, - // align: "center", - // backgroundColor: { - // image: `data:image/svg+xml;charset=utf-8;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNCIgaGVpZ2h0PSI1OCIgdmlld0JveD0iMCAwIDE0IDU4IiBmaWxsPSJub25lIj4KPHBhdGggZD0iTTEyLjczMDIgNDYuMDQzOVY1Ni4wNDM5SDIuNzMwMjIiIHN0cm9rZT0iI0ZGRkREMyIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiLz4KPHBhdGggZD0iTTIuNzMwMjIgMS4wNDM5NUgxMi43MzAyVjExLjA0MzkiIHN0cm9rZT0iI0ZGRkREMyIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiLz4KPHBhdGggZD0iTTEgMTkuMDQzOVYzOS4wNDM5IiBzdHJva2U9IiNGRkZERDMiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIi8+Cjwvc3ZnPg==`, // 动态生成国旗图标 - // }, - // }, - }, - backgroundColor: "transparent", - }, - silent: true, - data: mianLineData(mainToData), - } - ); - }; - - // 创建A点和B点,并添加飞线和标签 - const createSpecialPoints = (series: echarts.SeriesOption[]) => { - // 定义点A和点B的坐标 - const pointA = [-42.604303, 71.706936]; - const pointB = [-106.346771, 56.130366]; - const newPointB = [pointB[0] + 14, pointB[1] + 10]; - - // 添加A点 - 带涟漪效果的双层点 - series.push( - // 外层带涟漪效果的点 - { - type: "effectScatter", - coordinateSystem: "geo", - zlevel: 3, - color: "#FF6B01", // 橙色外层 - symbol: "circle", - symbolSize: 8, - rippleEffect: { - period: 8, // 动画时间 - brushType: "stroke", // 波纹绘制方式 - scale: 6, // 波纹圆环最大限制 - brushWidth: 2, - }, - label: { - show: true, - position: [10, -50], - formatter: () => { - return "{name1|待认证节点}"; - }, - rich: { - name1: { - color: "#FF6B01", - align: "center", - lineHeight: 35, - fontSize: 18, - fontWeight: 600, - padding: [11, 16.52, 11, 16.52], - backgroundColor: "rgba(63, 6, 3, 0.5)", - }, - }, - backgroundColor: "transparent", - }, - data: [ - { - name: "格陵兰", - value: pointA, - }, - ], - } as echarts.SeriesOption, - - // 内层白色点 - { - type: "scatter", // 普通scatter,不带特效 - coordinateSystem: "geo", - zlevel: 4, // 确保在外层点上方 - color: "#FFFFFF", // 白色内层 - symbol: "circle", - symbolSize: 4, - label: { - show: false, - }, - data: [ - { - name: "格陵兰", - value: pointA, - }, - ], - } as echarts.SeriesOption - ); - - // 添加B点 - 大型圆形区域 - series.push({ - type: "scatter", - coordinateSystem: "geo", - zlevel: 2, - color: "rgba(55, 255, 0, 0.50)", // 半透明绿色 - symbol: "circle", - symbolSize: 150, // 大尺寸圆形 - label: { - show: true, - position: [-70, -30], - formatter: () => { - return "{name2|权威节点团}"; - }, - rich: { - name2: { - color: "#37FF00", - align: "center", - lineHeight: 35, - fontSize: 18, - fontWeight: 600, - padding: [11, 16.52, 11, 16.52], - backgroundColor: "rgba(4, 59, 27, 0.5)", - }, - }, - backgroundColor: "transparent", - }, - data: [ - { - name: "加拿大", - value: pointB, - }, - ], - } as echarts.SeriesOption); - - // 添加A到B的飞线(无特效) - series.push({ - type: "lines", - zlevel: 1, - effect: { - show: false, // 关闭特效 - }, - lineStyle: { - curveness: -0.4, // 飞线弧度 - type: "solid", - color: "#FEAA18", // 飞线颜色 - width: 1.5, - opacity: 0.8, - }, - data: [ - { - coords: [pointA, newPointB], // 从A点到B点 - }, - ], - } as echarts.SeriesOption); - - // 计算飞线中点坐标(考虑曲率) - const x1 = pointA[0]; - const y1 = pointA[1]; - const x2 = newPointB[0]; - const y2 = newPointB[1]; - const curveness = -0.4; - - // 计算控制点 - const cpx = (x1 + x2) / 2 - (y2 - y1) * curveness; - const cpy = (y1 + y2) / 2 - (x1 - x2) * curveness; - - // 计算曲线上的中点 (t=0.5 时的贝塞尔曲线点) - const midX = x1 * 0.25 + cpx * 0.5 + x2 * 0.25; - const midY = y1 * 0.25 + cpy * 0.5 + y2 * 0.25; - - // 将中点添加到 lineMidpointsRef 中,以便使用 DOM 方式创建标签 - lineMidpointsRef.current.push({ - id: "special-line-label", - midpoint: [midX, midY], - fromCountry: "A", - toCountry: "B", - }); - - return series; - }; - - const getOption = () => { - const series: echarts.SeriesOption[] = []; - getLianData(series); - // getMianLineTipData(series); // 添加主线tip 暂时隐藏 - createSpecialPoints(series); // 添加特殊点和飞线 - - const option = { - backgroundColor: "transparent", - // 全局提示框配置 - tooltip: { - show: true, - trigger: "item", - enterable: true, - confine: true, // 保持提示框在图表范围内 - appendToBody: true, // 将提示框附加到body以获得更好的定位 - // position: function(pos:any, params, dom, rect, size) { - position: function (pos: any) { - // 自定义定位逻辑(如果需要) - return [pos[0] + 10, pos[1] - 50]; // 从光标偏移 - }, - }, - // 底图样式 - geo: { - map: "world", // 地图类型 - roam: true, // 是否开启缩放 - zoom: 1, // 初始缩放大小 - // center: [11.3316626, 19.5845024], // 地图中心点 - layoutCenter: ["50%", "50%"], //地图位置 - scaleLimit: { - // 缩放等级 - min: 1, - max: 3, - }, - label: { - show: false, - }, - nameMap: countryNameMap, // 自定义地区的名称映射 - // 三维地理坐标系样式 - itemStyle: { - areaColor: "#020617", // 修改为要求的填充颜色 - borderColor: "#cbd5e1", // 修改为要求的边框颜色 - borderWidth: 1, // 边框宽度 - borderType: "dashed", // 修改为点线边框 - }, - emphasis: { - itemStyle: { - areaColor: "#172554", // 修改为鼠标悬停时的填充颜色 - borderColor: "#0ea5e9", // 修改为鼠标悬停时的边框颜色 - borderWidth: 1.2, // 修改为鼠标悬停时的边框宽度 - borderType: "solid", // 修改为实线边框 - }, - label: false, - }, - tooltip: { - show: true, - trigger: "item", - triggerOn: "click", // 提示框触发的条件 - enterable: true, // 鼠标是否可进入提示框浮层中,默认为false,如需详情内交互,如添加链接,按钮,可设置为 true - backgroundColor: "rgba(0,0,0,0.8)", - borderColor: "rgba(0,0,0,0.2)", - textStyle: { - color: "#fff", - }, - formatter: (parameters: { - name: string; - data: - | { - name: string; - datas: { tradingCountry: string }; - } - | undefined; - }) => { - if (parameters.data?.name) - return parameters.data.name; - return parameters.name; - }, - }, - }, - series: series, - }; - return option; - }; - - // 创建DOM标签 - const createDOMLabels = () => { - // 清除现有标签 - if (labelContainerRef.current) { - labelContainerRef.current.innerHTML = ""; - labelsRef.current = []; - } else { - // 创建标签容器 - const container = document.createElement("div"); - container.className = "line-labels-container"; - container.style.position = "absolute"; - container.style.top = "0"; - container.style.left = "0"; - container.style.pointerEvents = "none"; - container.style.zIndex = "1000"; - container.style.width = "100%"; - container.style.height = "100%"; - container.style.overflow = "hidden"; - - // 添加到地图容器 - const chartDom = document.getElementById("screenGeo"); - if (chartDom) { - chartDom.style.position = "relative"; - chartDom.appendChild(container); - labelContainerRef.current = container; - } - } - - // 创建新标签 - lineMidpointsRef.current.forEach((point, index) => { - const label = document.createElement("div"); - label.id = point.id; - label.className = "line-label"; - label.style.position = "absolute"; - label.style.textAlign = "center"; - label.style.transform = "translate(-50%, -50%)"; - label.style.whiteSpace = "nowrap"; - label.style.pointerEvents = "none"; - label.style.zIndex = "1001"; - - // 特殊线标签(A到B的线) - if (point.id === "special-line-label") { - label.style.backgroundColor = "#8B3700"; - label.style.color = "#FFB27A"; - label.style.padding = "5px 10px"; - label.style.borderRadius = "4px"; - label.style.fontSize = "18px"; - label.style.fontWeight = "normal"; - label.textContent = "SS签名"; - } - // 其他线标签 - else { - label.style.backgroundColor = "#8B3700"; - label.style.color = "#FFB27A"; - label.style.padding = "5px 10px"; - label.style.borderRadius = "4px"; - label.style.fontSize = "18px"; - label.style.fontWeight = "normal"; - label.textContent = "SS签名"; - } - - // 添加到容器 - labelContainerRef.current?.appendChild(label); - labelsRef.current.push(label); - }); - - // 更新标签位置 - updateLabelPositions(); - }; - - // 更新标签位置 - const updateLabelPositions = () => { - if (!proxyGeoRef.current || !labelContainerRef.current) return; - - lineMidpointsRef.current.forEach((point, index) => { - const label = labelsRef.current[index]; - if (!label) return; - - const pixelPoint = proxyGeoRef.current?.convertToPixel( - "geo", - point.midpoint - ); - if (pixelPoint && Array.isArray(pixelPoint)) { - label.style.left = `${pixelPoint[0]}px`; - label.style.top = `${pixelPoint[1]}px`; - } - }); - }; - - const handleResize = () => { - proxyGeoRef.current?.resize(); - updateLabelPositions(); - }; - - useEffect(() => { - preMainToData.current?.some( - (item, index) => - item.country_code !== mainToData[index]?.country_code - ) && proxyGeoRef.current?.clear(); - preMainToData.current = mainToData; - const option = getOption(); - proxyGeoRef.current?.setOption(option); - - // 创建DOM标签 - setTimeout(createDOMLabels, 100); - }, [newHomeProxies, mainToData]); - - useEffect(() => { - const chartDom = document.getElementById("screenGeo"); - proxyGeoRef.current = echarts.init(chartDom); - echarts.registerMap( - "world", - worldGeoJson as unknown as Parameters< - typeof echarts.registerMap - >[1] - ); - const option = getOption(); - option && proxyGeoRef.current?.setOption(option); - - // 添加地图交互事件监听器 - proxyGeoRef.current?.on("georoam", updateLabelPositions); - - // 页面resize时触发 - window.addEventListener("resize", handleResize); - return () => { - window.removeEventListener("resize", handleResize); - proxyGeoRef.current?.off("georoam", updateLabelPositions); - - // 清理DOM标签 - if (labelContainerRef.current) { - labelContainerRef.current.remove(); - labelContainerRef.current = null; - labelsRef.current = []; - } - - proxyGeoRef.current?.dispose(); - proxyGeoRef.current = null; - }; - }, []); - - useEffect(() => { - if (tooltipClosed) { - createCustomTooltip(); - } - }, [tooltipClosed, tooltipType]); - - return ( -
-
-
+ }, + backgroundColor: "transparent", + borderWidth: 0, + }, + data: ripplePoints.map((point: any) => ({ + name: point.name, + value: point.value, + datas: { + country_code: point.country_code, + color: point.color || "#0ea5e9" // 保存颜色信息 + }, + })), + } as echarts.SeriesOption; + }; + // 连线 series + const getLianData = (series: echarts.SeriesOption[]) => { + const { solidData, otherLineList, ripplePoints } = getLine(); + // 如果有需要显示涟漪效果的点,添加它们 + if (ripplePoints.length > 0) { + // 添加带自定义提示框的外层彩色点 + series.push(createRipplePointsWithTooltip(ripplePoints)); + // 添加内层白色点,不带涟漪效果 + series.push({ + type: "scatter", // 使用普通scatter,不带特效 + coordinateSystem: "geo", + zlevel: 4, // 确保在彩色点上方 + color: "#FFFFFF", // 白色内层 + symbol: "circle", + symbolSize: 4, + label: { + show: false, + }, + data: ripplePoints.map((point) => ({ + name: point.name, + value: point.value, + datas: { + country_code: point.country_code, + }, + })), + } as echarts.SeriesOption); + } + solidData.forEach((item) => { + // 如果没有连线数据,则跳过 + if (item[1].length === 0) { + return; + } + const lastExit = item[1]?.[item[1].length - 1]?.[1] ?? null; + 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 的值,数值越大尾迹越长 + 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({ + name: item[0], + type: "lines", + zlevel: 1, + label: { + show: false, + }, + // 线条样式 + lineStyle: { + curveness: -0.4, // 飞线弧度 + type: [5, 5], // 飞线类型 + color: "#F0FFA2", // 飞线颜色 + width: 0.5, // 飞线宽度 + opacity: 0.6, + }, + data: convertData(item[1]) as echarts.LinesSeriesOption["data"], + }); + // 添加路径点的双层效果(次要路径) + const pathPoints = createPathPoints(item[1], false); + series.push(...pathPoints); + // 添加出口节点的双层效果(次要路径) + if (lastExit) { + const exitNodes = createDualLayerPoint(lastExit, false, lastExitColor); + series.push(...exitNodes); + } + }); + }); + return true; + }; + // 主线tip series + const getMianLineTipData = (series: echarts.SeriesOption[] = []) => { + series.push( + // 柱状体的主干 + { + name: "solidTip", + type: "lines", + zlevel: 5, + effect: { + show: false, + symbolSize: 5, // 图标大小 + }, + lineStyle: { + width: 0, // 尾迹线条宽度 + color: "#F0FFA2", + opacity: 1, // 尾迹线条透明度 + curveness: 0, // 尾迹线条曲直度 + }, + label: { + show: true, + position: "end", + color: "#fff", + formatter: (parameters) => { + const isFr = parameters.value === "FR"; + console.log(parameters, "parameters"); + const name = isFr ? "权威节点团" : "待认证节点"; + return `{left|}{gap2|}{name${ + isFr ? "2" : "1" + }|${name}}{gap3|}{right|}`; + }, + rich: { + gap1: { + height: 35, + width: 8, + }, + gap2: { + height: 35, + width: 6, + }, + name1: { + color: "#FF6B01", + align: "center", + lineHeight: 35, + fontSize: 18, + fontWeight: 600, + padding: [11, 16.52, 11, 16.52], + backgroundColor: "rgba(63, 6, 3, 0.5)", + }, + name2: { + color: "#37FF00", + align: "center", + lineHeight: 35, + fontSize: 18, + fontWeight: 600, + padding: [11, 16.52, 11, 16.52], + backgroundColor: "rgba(4, 59, 27, 0.5)", + }, + gap3: { + height: 35, + width: 8, + }, + }, + backgroundColor: "transparent", + }, + silent: true, + data: mianLineData(mainToData), + } + ); + }; + // 创建A点和B点,并添加飞线和标签 + const createSpecialPoints = (series: echarts.SeriesOption[]) => { + // 定义点A和点B的坐标 + const pointA = [-42.604303, 71.706936]; + const pointB = [-106.346771, 56.130366]; + const newPointB = [pointB[0] + 14, pointB[1] + 10]; + // 添加A点 - 带涟漪效果的双层点 + series.push( + // 外层带涟漪效果的点 + { + type: "effectScatter", + coordinateSystem: "geo", + zlevel: 3, + color: "#FF6B01", // 橙色外层 + symbol: "circle", + symbolSize: 8, + rippleEffect: { + period: 8, // 动画时间 + brushType: "stroke", // 波纹绘制方式 + scale: 6, // 波纹圆环最大限制 + brushWidth: 2, + }, + label: { + show: true, + position: [10, -50], + formatter: () => { + return "{name1|待认证节点}"; + }, + rich: { + name1: { + color: "#FF6B01", + align: "center", + lineHeight: 35, + fontSize: 18, + fontWeight: 600, + padding: [11, 16.52, 11, 16.52], + backgroundColor: "rgba(63, 6, 3, 0.5)", + }, + }, + backgroundColor: "transparent", + }, + data: [ + { + name: "格陵兰", + value: pointA, + }, + ], + } as echarts.SeriesOption, + // 内层白色点 + { + type: "scatter", // 普通scatter,不带特效 + coordinateSystem: "geo", + zlevel: 4, // 确保在外层点上方 + color: "#FFFFFF", // 白色内层 + symbol: "circle", + symbolSize: 4, + label: { + show: false, + }, + data: [ + { + name: "格陵兰", + value: pointA, + }, + ], + } as echarts.SeriesOption + ); + // 添加B点 - 大型圆形区域 + series.push({ + type: "scatter", + coordinateSystem: "geo", + zlevel: 2, + color: "rgba(55, 255, 0, 0.50)", // 半透明绿色 + symbol: "circle", + symbolSize: 150, // 大尺寸圆形 + label: { + show: true, + position: [-70, -30], + formatter: () => { + return "{name2|权威节点团}"; + }, + rich: { + name2: { + color: "#37FF00", + align: "center", + lineHeight: 35, + fontSize: 18, + fontWeight: 600, + padding: [11, 16.52, 11, 16.52], + backgroundColor: "rgba(4, 59, 27, 0.5)", + }, + }, + backgroundColor: "transparent", + }, + data: [ + { + name: "加拿大", + value: pointB, + }, + ], + } as echarts.SeriesOption); + // 添加A到B的飞线(无特效) + series.push({ + type: "lines", + zlevel: 1, + effect: { + show: false, // 关闭特效 + }, + lineStyle: { + curveness: -0.4, // 飞线弧度 + type: "solid", + color: "#FEAA18", // 飞线颜色 + width: 1.5, + opacity: 0.8, + }, + data: [ + { + coords: [pointA, newPointB], // 从A点到B点 + }, + ], + } as echarts.SeriesOption); + // 计算飞线中点坐标(考虑曲率) + const x1 = pointA[0]; + const y1 = pointA[1]; + const x2 = newPointB[0]; + const y2 = newPointB[1]; + const curveness = -0.4; + // 计算控制点 + const cpx = (x1 + x2) / 2 - (y2 - y1) * curveness; + const cpy = (y1 + y2) / 2 - (x1 - x2) * curveness; + // 计算曲线上的中点 (t=0.5 时的贝塞尔曲线点) + const midX = x1 * 0.25 + cpx * 0.5 + x2 * 0.25; + const midY = y1 * 0.25 + cpy * 0.5 + y2 * 0.25; + // 将中点添加到 lineMidpointsRef 中,以便使用 DOM 方式创建标签 + lineMidpointsRef.current.push({ + id: "special-line-label", + midpoint: [midX, midY], + fromCountry: "A", + toCountry: "B", + }); + return series; + }; + const getOption = () => { + const series: echarts.SeriesOption[] = []; + getLianData(series); + // getMianLineTipData(series); // 添加主线tip 暂时隐藏 + createSpecialPoints(series); // 添加特殊点和飞线 + if (newHomeProxies[0]?.authenticationPoint) { + createRipplePointsFromCoordinates( + newHomeProxies[0]?.authenticationPoint || [], + series ); - } -); + } + const option = { + backgroundColor: "transparent", + // 全局提示框配置 + tooltip: { + show: true, + trigger: "item", + enterable: true, + confine: true, // 保持提示框在图表范围内 + appendToBody: true, // 将提示框附加到body以获得更好的定位 + // position: function(pos:any, params, dom, rect, size) { + position: function (pos: any) { + // 自定义定位逻辑(如果需要) + return [pos[0] + 10, pos[1] - 50]; // 从光标偏移 + }, + }, + // 底图样式 + geo: { + map: "world", // 地图类型 + roam: true, // 是否开启缩放 + zoom: 1, // 初始缩放大小 + // center: [11.3316626, 19.5845024], // 地图中心点 + layoutCenter: ["50%", "50%"], //地图位置 + scaleLimit: { + // 缩放等级 + min: 1, + max: 3, + }, + label: { + show: false, + }, + nameMap: countryNameMap, // 自定义地区的名称映射 + // 三维地理坐标系样式 + itemStyle: { + areaColor: "#020617", // 修改为要求的填充颜色 + borderColor: "#cbd5e1", // 修改为要求的边框颜色 + borderWidth: 1, // 边框宽度 + borderType: "dashed", // 修改为点线边框 + }, + emphasis: { + itemStyle: { + areaColor: "#172554", // 修改为鼠标悬停时的填充颜色 + borderColor: "#0ea5e9", // 修改为鼠标悬停时的边框颜色 + borderWidth: 1.2, // 修改为鼠标悬停时的边框宽度 + borderType: "solid", // 修改为实线边框 + }, + label: false, + }, + tooltip: { + show: true, + trigger: "item", + triggerOn: "click", // 提示框触发的条件 + enterable: true, // 鼠标是否可进入提示框浮层中,默认为false,如需详情内交互,如添加链接,按钮,可设置为 true + backgroundColor: "rgba(0,0,0,0.8)", + borderColor: "rgba(0,0,0,0.2)", + textStyle: { + color: "#fff", + }, + formatter: (parameters: { + name: string; + data: + | { + name: string; + datas: { tradingCountry: string }; + } + | undefined; + }) => { + if (parameters.data?.name) return parameters.data.name; + return parameters.name; + }, + }, + }, + series: series, + }; + return option; + }; + // 创建DOM标签 + const createDOMLabels = () => { + // 清除现有标签 + if (labelContainerRef.current) { + labelContainerRef.current.innerHTML = ""; + labelsRef.current = []; + } else { + // 创建标签容器 + const container = document.createElement("div"); + container.className = "line-labels-container"; + container.style.position = "absolute"; + container.style.top = "0"; + container.style.left = "0"; + container.style.pointerEvents = "none"; + container.style.zIndex = "1000"; + container.style.width = "100%"; + container.style.height = "100%"; + container.style.overflow = "hidden"; + // 添加到地图容器 + const chartDom = document.getElementById("screenGeo"); + if (chartDom) { + chartDom.style.position = "relative"; + chartDom.appendChild(container); + labelContainerRef.current = container; + } + } + // 创建新标签 + lineMidpointsRef.current.forEach((point, index) => { + const label = document.createElement("div"); + label.id = point.id; + label.className = "line-label"; + label.style.position = "absolute"; + label.style.textAlign = "center"; + label.style.transform = "translate(-50%, -50%)"; + label.style.whiteSpace = "nowrap"; + label.style.pointerEvents = "none"; + label.style.zIndex = "1001"; + // 特殊线标签(A到B的线) + if (point.id === "special-line-label") { + label.style.backgroundColor = "#8B3700"; + label.style.color = "#FFB27A"; + label.style.padding = "5px 10px"; + label.style.borderRadius = "4px"; + label.style.fontSize = "18px"; + label.style.fontWeight = "normal"; + label.textContent = "SS签名"; + } + // 其他线标签 + else { + label.style.backgroundColor = "#8B3700"; + label.style.color = "#FFB27A"; + label.style.padding = "5px 10px"; + label.style.borderRadius = "4px"; + label.style.fontSize = "18px"; + label.style.fontWeight = "normal"; + label.textContent = "SS签名"; + } + // 添加到容器 + labelContainerRef.current?.appendChild(label); + labelsRef.current.push(label); + }); + // 更新标签位置 + updateLabelPositions(); + }; + // 更新标签位置 + const updateLabelPositions = () => { + if (!proxyGeoRef.current || !labelContainerRef.current) return; + lineMidpointsRef.current.forEach((point, index) => { + const label = labelsRef.current[index]; + if (!label) return; + const pixelPoint = proxyGeoRef.current?.convertToPixel( + "geo", + point.midpoint + ); + if (pixelPoint && Array.isArray(pixelPoint)) { + label.style.left = `${pixelPoint[0]}px`; + label.style.top = `${pixelPoint[1]}px`; + } + }); + }; + const handleResize = () => { + proxyGeoRef.current?.resize(); + updateLabelPositions(); + }; + useEffect(() => { + preMainToData.current?.some( + (item, index) => item.country_code !== mainToData[index]?.country_code + ) && proxyGeoRef.current?.clear(); + preMainToData.current = mainToData; + const option = getOption(); + proxyGeoRef.current?.setOption(option); + // 创建DOM标签 + setTimeout(createDOMLabels, 100); + }, [newHomeProxies, mainToData]); + useEffect(() => { + const chartDom = document.getElementById("screenGeo"); + proxyGeoRef.current = echarts.init(chartDom); + echarts.registerMap( + "world", + worldGeoJson as unknown as Parameters[1] + ); + const option = getOption(); + option && proxyGeoRef.current?.setOption(option); + // 添加地图交互事件监听器 + proxyGeoRef.current?.on("georoam", updateLabelPositions); + // 页面resize时触发 + window.addEventListener("resize", handleResize); + return () => { + window.removeEventListener("resize", handleResize); + proxyGeoRef.current?.off("georoam", updateLabelPositions); + // 清理DOM标签 + if (labelContainerRef.current) { + labelContainerRef.current.remove(); + labelContainerRef.current = null; + labelsRef.current = []; + } + proxyGeoRef.current?.dispose(); + proxyGeoRef.current = null; + }; + }, []); + useEffect(() => { + console.log(tooltipClosed,'tooltipClosedtooltipClosed') + if (tooltipClosed) { + createCustomTooltip(); + createCustomTooltip2(); + } else { + customTooltipRef.current?.remove(); + customTooltip2Ref.current?.remove(); + customTooltipRef.current = null; + customTooltip2Ref.current = null; + } + return () => { + customTooltipRef.current?.remove(); + customTooltip2Ref.current?.remove(); + customTooltipRef.current = null; + customTooltip2Ref.current = null; + }; + }, [tooltipClosed, tooltipType]); + return ( +
+
+
+ ); + } +); \ No newline at end of file diff --git a/src/pages/new-home/index.tsx b/src/pages/new-home/index.tsx index e8152df..1790d08 100644 --- a/src/pages/new-home/index.tsx +++ b/src/pages/new-home/index.tsx @@ -9,9 +9,7 @@ import Web3Box2Png from "@/assets/image/home/web3-box2.png"; import OpenProxyPng from "@/assets/image/home/open-proxy.png"; import web3BoxGif from "@/assets/gif/web3-box-bg.gif"; import VectorSlideSvg from "@/assets/svg/home/vector-solide.svg?react"; -import AddSvg from "@/assets/svg/home/add.svg?react"; -import InterSvg from "@/assets/svg/home/inter.svg?react"; -import TrashSvg from "@/assets/svg/home/trash.svg?react"; + import { Apps, CONST_TOOLTIP_TYPE } from "@/pages/anti-forensics-forwarding"; import { cn } from "@/lib/utils"; @@ -20,13 +18,13 @@ import { setProxyInfoProxies, setProxiesList1, setProxiesList2, - setProxiesLine, } from "@/store/web3Slice"; import type { AppDispatch, RootState } from "@/store"; import "./index.scss"; import { DialogConfig, FormAlertDialog } from "./components/FormAlertDialog"; import { ClearNodeDialog } from "./components/ClearNodeDialog"; import { blockChainApi } from "@/api/block"; +import { APP_DIVERSION } from "../anti-forensics-forwarding/data/mockData"; export const DIALOGTYPE = { ADDNode: { @@ -71,12 +69,24 @@ const NewHome = () => { const [blockChain, setBlockChain] = useState(null); - const [tooltipClosed, setTooltipClosed] = useState(false); + const [tooltipClosed, setTooltipClosed] = useState(true); const [tooltipType, setTooltipType] = useState( CONST_TOOLTIP_TYPE.NESTED_ENCRYPTION.type ); + const appDiversion = useMemo(() => { + return Apps.map((item) => { + const findApp = APP_DIVERSION.find( + (appItem) => item.name === appItem.name + ); + return { + ...item, + ...findApp, + }; + }); + }, []); + const newWeb3List = useMemo(() => { // 展示最新的6个节点 return web3List.slice(-6); @@ -102,7 +112,6 @@ const NewHome = () => { }; const handleClickApp = (item: any) => { - console.log("item", item); setSelectedApp(item); }; @@ -178,12 +187,14 @@ const NewHome = () => { console.log("res",res) setBlockChain(res) }) + },[]) + + return (
-
{blockChain?.block_id?.hash || "111"}
{/* {
-
{ setTooltipType( @@ -328,8 +339,8 @@ const NewHome = () => { }} > 流量混淆 -
- {Apps.map((item) => { +
*/} + {appDiversion.map((item) => { return (
{ - const value = Math.random() * 100; - // 50% 的概率返回整数,50% 的概率返回一位小数 - return Math.random() > 0.5 - ? Math.floor(value).toString() - : value.toFixed(1); + const value = Math.random() * 100; + // 50% 的概率返回整数,50% 的概率返回一位小数 + return Math.random() > 0.5 ? Math.floor(value).toString() : value.toFixed(1); }; // 随机生成 1-60 的整数 const randomUpdatedAt = (): number => { - return Math.floor(Math.random() * 60) + 1; + return Math.floor(Math.random() * 60) + 1; }; // 随机生成 20-100 的整数 const randomTransactionCount = (): string => { - return (Math.floor(Math.random() * 100) + 20).toString(); + return (Math.floor(Math.random() * 100) + 20).toString(); }; // 随机生成20-100的整数 const randomTransactionCount2 = (): string => { - return (Math.floor(Math.random() * 80) + 20).toString(); + return (Math.floor(Math.random() * 80) + 20).toString(); }; // 更新钱包数据的函数 const updateWalletData = (wallet: Iweb3): Iweb3 => { - return { - ...wallet, - balance: randomBalance(), - upDatedAt: randomUpdatedAt(), - transactions: randomTransactionCount2(), - numberTransactions: randomTransactionCount(), - }; + return { + ...wallet, + balance: randomBalance(), + upDatedAt: randomUpdatedAt(), + transactions: randomTransactionCount2(), + numberTransactions: randomTransactionCount(), + }; }; const initialState: Iweb3Slice = { - isLine: false, - web3List: [], - web3List2: [ + isLine: false, + web3List: [], + web3List2: [ + { + id: "6", + name: "Cardano Wallet", + payType: "ADA", + status: "active", + createdAt: 1737436420, + upDatedAt: 5, + balance: "65", + address: + "addr1qxck6ztj8lrxd0j2jz8f7tznzfu9wqv9qrplrh3r9eq8g9n0n3anjy2a4x54kd2sort3qvnc7mct82krlnpnxvl7v3sxmrv3f", + privateKey: + "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi", + publicKey: + "addr1qxck6ztj8lrxd0j2jz8f7tznzfu9wqv9qrplrh3r9eq8g9n0n3anjy2a4x54kd2sort3qvnc7mct82krlnpnxvl7v3sxmrv3f", + numberTransactions: "35", + transactions: 1, + }, + ], + path_list: [ + { + account: "admin", + account_is_admin: true, + exclusive: "none", + name: "default(10.66.66.234)-c250", + proxies: [], + proxies_code: [], + use: true, + current_ip_use: false, + }, + ], + + proxy_info: { + exclusive: "", + name: "default(10.66.66.234)-c250", + wg: false, + change_time: 0, + change_at: 0, + proxies: [], + }, + clearWarningTimer: null, + clearFailTimer: null, + newHomeProxies: [ + { + authenticationPoint: [ + [-103.346771, 54.130366], + [-120.346771, 52.130366], + [-108.346771, 48.130366], + [-98.346771, 46.130366], + [-106.346771, 48.450366], + [-101.346771, 53.130366], + [-123.346771, 58.130366], + [-111.346771, 65.443366], + [-108.346771, 54.130366], + [-116.346771, 59.130366], + [-97.346771, 61.130366], + [-95.346771, 63.130366], + [-113.346771, 58.840366], + [-99.346771, 59.130366], + [-102.346771, 68.130366], + ], + data: [ { - id: "6", + 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, + name: "newHomeProxies", + }, + ], +}; + +export const appSlice = createSlice({ + name: "web3", + initialState, + reducers: { + setProxiesList1: (state) => { + // state.proxy_info.prox + // 判断是否已经存在 + const proxies = state.proxy_info.proxies.find( + (item: any) => item.name === "data1" + ); + if (!proxies) { + state.proxy_info.proxies.push(data1); + } + }, + setProxiesList2: (state) => { + // state.proxy_info.prox + // 判断是否已经存在 + const proxies = state.proxy_info.proxies.find( + (item: any) => item.name === "data2" + ); + if (!proxies) { + state.proxy_info.proxies.push(data2); + } + }, + setProxiesLine: (state) => { + if (state.proxy_info.proxies.length === 0) return; + // 判断一下如果state.proxy_info.proxies.lengt === 2并且 web3List.length ===1 那么 添加一个web3 + // 如果state.proxy_info.proxies.lengt === 2并且 web3List.length ===2 那么不添加web3 + // 如果state.proxy_info.proxies.lengt === 1并且 web3List.length ===1 那么添加一个web3 + // 如果state.proxy_info.proxies.lengt === 2并且 web3List.length ===0 那么添加两个web3 + // 如果state.proxy_info.proxies.lengt === 1并且 web3List.length ===0 那么添加一个web3 + + // 进一步优化的代码 + if (state.proxy_info.proxies.length === 0) return; + + // 标记所有代理为在线 - 这个操作仍然需要 + state.proxy_info.proxies = state.proxy_info.proxies.map((item: any) => { + item.isLine = true; + return item; + }); + + // 计算需要添加的钱包数量 + const proxiesCount = state.proxy_info.proxies.length; + const currentWeb3Count = state.web3List.length; + + // 检查是否需要添加钱包 + if ( + (proxiesCount === 2 && currentWeb3Count >= 2) || + (proxiesCount === 1 && currentWeb3Count >= 1) + ) { + // 已满足条件,不需要任何操作 + return; + } + + // 计算需要添加的钱包数量 + let walletsToAdd = 0; + if (proxiesCount === 2) { + walletsToAdd = 2 - currentWeb3Count; // 最多添加到2个 + } else if (proxiesCount === 1) { + walletsToAdd = 1 - currentWeb3Count; // 最多添加到1个 + } + + // 确保不会添加负数的钱包 + walletsToAdd = Math.max(0, walletsToAdd); + + // 只有在需要添加钱包时才创建新钱包 + if (walletsToAdd > 0) { + const newWallets: Iweb3[] = []; + for (let i = 0; i < walletsToAdd; i++) { + const id = uuid(); + newWallets.push({ + id, name: "Cardano Wallet", payType: "ADA", status: "active", createdAt: 1737436420, - upDatedAt: 5, - balance: "65", - address: - "addr1qxck6ztj8lrxd0j2jz8f7tznzfu9wqv9qrplrh3r9eq8g9n0n3anjy2a4x54kd2sort3qvnc7mct82krlnpnxvl7v3sxmrv3f", - privateKey: - "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi", - publicKey: - "addr1qxck6ztj8lrxd0j2jz8f7tznzfu9wqv9qrplrh3r9eq8g9n0n3anjy2a4x54kd2sort3qvnc7mct82krlnpnxvl7v3sxmrv3f", - numberTransactions: "35", - transactions: 1, - }, - ], - path_list: [ - { - account: "admin", - account_is_admin: true, - exclusive: "none", - name: "default(10.66.66.234)-c250", - proxies: [], - proxies_code: [], - use: true, - current_ip_use: false, - }, - ], + balance: randomBalance(), + upDatedAt: randomUpdatedAt(), + transactions: randomTransactionCount2(), + numberTransactions: randomTransactionCount(), + }); + } - proxy_info: { - exclusive: "", - name: "default(10.66.66.234)-c250", - wg: false, - change_time: 0, - change_at: 0, - proxies: [], + // 更新状态 + state.web3List = [...state.web3List, ...newWallets]; + console.log(state.web3List, "state.web3List"); + } }, - clearWarningTimer: null, - clearFailTimer: null, - newHomeProxies:[ - { - 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, - name: "newHomeProxies", - } - ], -}; - -export const appSlice = createSlice({ - name: "web3", - initialState, - reducers: { - setProxiesList1: (state) => { - // state.proxy_info.prox - // 判断是否已经存在 - const proxies = state.proxy_info.proxies.find( - (item: any) => item.name === "data1" - ); - if (!proxies) { - state.proxy_info.proxies.push(data1); - } - }, - setProxiesList2: (state) => { - // state.proxy_info.prox - // 判断是否已经存在 - const proxies = state.proxy_info.proxies.find( - (item: any) => item.name === "data2" - ); - if (!proxies) { - state.proxy_info.proxies.push(data2); - } - }, - setProxiesLine: (state) => { - if (state.proxy_info.proxies.length === 0) return; - // 判断一下如果state.proxy_info.proxies.lengt === 2并且 web3List.length ===1 那么 添加一个web3 - // 如果state.proxy_info.proxies.lengt === 2并且 web3List.length ===2 那么不添加web3 - // 如果state.proxy_info.proxies.lengt === 1并且 web3List.length ===1 那么添加一个web3 - // 如果state.proxy_info.proxies.lengt === 2并且 web3List.length ===0 那么添加两个web3 - // 如果state.proxy_info.proxies.lengt === 1并且 web3List.length ===0 那么添加一个web3 - - // 进一步优化的代码 - if (state.proxy_info.proxies.length === 0) return; - - // 标记所有代理为在线 - 这个操作仍然需要 - state.proxy_info.proxies = state.proxy_info.proxies.map( - (item: any) => { - item.isLine = true; - return item; - } - ); - - // 计算需要添加的钱包数量 - const proxiesCount = state.proxy_info.proxies.length; - const currentWeb3Count = state.web3List.length; - - // 检查是否需要添加钱包 - if ( - (proxiesCount === 2 && currentWeb3Count >= 2) || - (proxiesCount === 1 && currentWeb3Count >= 1) - ) { - // 已满足条件,不需要任何操作 - return; - } - - // 计算需要添加的钱包数量 - let walletsToAdd = 0; - if (proxiesCount === 2) { - walletsToAdd = 2 - currentWeb3Count; // 最多添加到2个 - } else if (proxiesCount === 1) { - walletsToAdd = 1 - currentWeb3Count; // 最多添加到1个 - } - - // 确保不会添加负数的钱包 - walletsToAdd = Math.max(0, walletsToAdd); - - // 只有在需要添加钱包时才创建新钱包 - if (walletsToAdd > 0) { - const newWallets: Iweb3[] = []; - for (let i = 0; i < walletsToAdd; i++) { - const id = uuid(); - newWallets.push({ - id, - name: "Cardano Wallet", - payType: "ADA", - status: "active", - createdAt: 1737436420, - balance: randomBalance(), - upDatedAt: randomUpdatedAt(), - transactions: randomTransactionCount2(), - numberTransactions: randomTransactionCount(), - }); - } - - // 更新状态 - state.web3List = [...state.web3List, ...newWallets]; - console.log(state.web3List, "state.web3List"); - } - }, - setIsLine: (state, action) => { - state.isLine = action.payload; - }, - setClearWarningTimer: (state, action) => { - state.clearWarningTimer = action.payload; - }, - setClearFailTimer: (state, action) => { - state.clearFailTimer = action.payload; - }, - setWeb3List: (state, action) => { - state.web3List = action.payload; - }, - setWeb3List2: (state, action) => { - state.web3List2 = action.payload; - }, - setProxyInfoProxies: (state, action) => { - state.proxy_info.proxies[0].data.push(action.payload); - console.log(action.payload, "action"); - // state.proxy_info.proxies = action.payload; - }, - randomUpdateWeb3List: (state) => { - state.web3List = state.web3List.map((wallet) => - updateWalletData(wallet) - ); - }, - randomUpdateWeb3List2: (state) => { - state.web3List2 = state.web3List2.map((wallet) => - updateWalletData(wallet) - ); - }, - reset: () => { - return initialState; - }, + setIsLine: (state, action) => { + state.isLine = action.payload; }, + setClearWarningTimer: (state, action) => { + state.clearWarningTimer = action.payload; + }, + setClearFailTimer: (state, action) => { + state.clearFailTimer = action.payload; + }, + setWeb3List: (state, action) => { + state.web3List = action.payload; + }, + setWeb3List2: (state, action) => { + state.web3List2 = action.payload; + }, + setProxyInfoProxies: (state, action) => { + state.proxy_info.proxies[0].data.push(action.payload); + console.log(action.payload, "action"); + // state.proxy_info.proxies = action.payload; + }, + randomUpdateWeb3List: (state) => { + state.web3List = state.web3List.map((wallet) => updateWalletData(wallet)); + }, + randomUpdateWeb3List2: (state) => { + state.web3List2 = state.web3List2.map((wallet) => + updateWalletData(wallet) + ); + }, + reset: () => { + return initialState; + }, + }, }); export const { - setWeb3List, - setWeb3List2, - setProxyInfoProxies, - randomUpdateWeb3List, - randomUpdateWeb3List2, - setClearWarningTimer, - setClearFailTimer, - reset, - setIsLine, - setProxiesList1, - setProxiesList2, - setProxiesLine, + setWeb3List, + setWeb3List2, + setProxyInfoProxies, + randomUpdateWeb3List, + randomUpdateWeb3List2, + setClearWarningTimer, + setClearFailTimer, + reset, + setIsLine, + setProxiesList1, + setProxiesList2, + setProxiesLine, } = appSlice.actions; export default appSlice.reducer;