diff --git a/src/api/jyh/situation.ts b/src/api/jyh/situation.ts index 3f6234d..e9863c5 100644 --- a/src/api/jyh/situation.ts +++ b/src/api/jyh/situation.ts @@ -66,7 +66,7 @@ export function mapDataPage1(params?: any): Promise { } // 全球开源风险态势感知监控中心-行业分布饼图 -export function dustryDistributionPage1(params?: any): Promise { +export function industryDistributionPage1(params?: any): Promise { return request({ url: `/api/v1/page1/industryDistribution`, method: 'get', diff --git a/src/views/Jyh/security/StatsGlass.vue b/src/views/Jyh/security/StatsGlass.vue index 3344d0e..b2d284e 100644 --- a/src/views/Jyh/security/StatsGlass.vue +++ b/src/views/Jyh/security/StatsGlass.vue @@ -23,7 +23,7 @@
- {{ formatProjectValue(stats.project) }} + {{ stats.project.toLocaleString() }}亿+ 全球开源项目
diff --git a/src/views/Jyh/security/components/IndustryDistribution.vue b/src/views/Jyh/security/components/IndustryDistribution.vue index e109e52..7a192dc 100644 --- a/src/views/Jyh/security/components/IndustryDistribution.vue +++ b/src/views/Jyh/security/components/IndustryDistribution.vue @@ -13,6 +13,7 @@ + + \ No newline at end of file diff --git a/src/views/Jyh/security/components/ThreatIntelMap.vue b/src/views/Jyh/security/components/ThreatIntelMap.vue index 0038b2e..38378b3 100644 --- a/src/views/Jyh/security/components/ThreatIntelMap.vue +++ b/src/views/Jyh/security/components/ThreatIntelMap.vue @@ -60,19 +60,14 @@ Vue 3 适配版代码 import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue'; import * as echarts from 'echarts'; import worldJson from './world.json'; +import { mapVulnStatsPage1, mapRegionStatsPage1, mapDataPage1, mapVulnTypeStatsPage1 } from '@/api/jyh/situation'; // 响应式数据 const chartDiv = ref(null); let myChart = null; let timer = null; -const vulnStats = ref([ - { name: 'XSS 跨站脚本', value: 32.1, color: '#ef4444' }, - { name: 'SQL 注入', value: 11.9, color: '#f97316' }, - { name: '内存缓冲区溢出', value: 10.9, color: '#eab308' }, - { name: '输入验证不当', value: 9.3, color: '#3b82f6' }, - { name: '信息泄露', value: 8.0, color: '#10b981' }, -]); +const vulnStats = ref([]); const regionStats = ref([ { name: 'United States', score: 435202 }, @@ -82,11 +77,8 @@ const regionStats = ref([ { name: 'Germany', score: 126397 }, ]); -const alerts = [ - { title: "高危漏洞通告", desc: "Apache Log4j2 远程代码执行漏洞复现" }, - { title: "僵尸网络活动", desc: "Mirai 变种正在扫描 IoT 设备端口 23" }, - { title: "APT 组织活动", desc: "OceanLotus 组织针对金融行业的钓鱼攻击" }, -]; +// 实时情报数据状态 +const alerts = ref([]); const currentAlert = ref(null); let alertIndex = 0; @@ -100,35 +92,116 @@ const geoCoordMap = { 'Moscow': [37.6173, 55.7558], }; +// 获取实时情报数据 +const fetchAlertsData = async () => { + try { + const response = await mapVulnTypeStatsPage1(); + if (response.code === 200) { + alerts.value = response.data || []; + if (alerts.value.length > 0) { + currentAlert.value = alerts.value[0]; + } + } else { + console.error('获取实时情报数据失败:', response.msg); + // 如果接口调用失败,使用默认数据 + alerts.value = [ + { title: "高危漏洞通告", desc: "Apache Log4j2 远程代码执行漏洞复现" }, + { title: "僵尸网络活动", desc: "Mirai 变种正在扫描 IoT 设备端口 23" }, + { title: "APT 组织活动", desc: "OceanLotus 组织针对金融行业的钓鱼攻击" }, + ]; + if (alerts.value.length > 0) { + currentAlert.value = alerts.value[0]; + } + } + } catch (error) { + console.error('请求实时情报数据时发生错误:', error); + // 发生异常时使用默认数据 + alerts.value = [ + { title: "高危漏洞通告", desc: "Apache Log4j2 远程代码执行漏洞复现" }, + { title: "僵尸网络活动", desc: "Mirai 变种正在扫描 IoT 设备端口 23" }, + { title: "APT 组织活动", desc: "OceanLotus 组织针对金融行业的钓鱼攻击" }, + ]; + if (alerts.value.length > 0) { + currentAlert.value = alerts.value[0]; + } + } +}; + // 逻辑方法 const startAlertRotation = () => { - currentAlert.value = alerts[0]; + if (alerts.value.length > 0) { + currentAlert.value = alerts.value[0]; + } timer = setInterval(() => { - alertIndex = (alertIndex + 1) % alerts.length; - currentAlert.value = null; - setTimeout(() => { - currentAlert.value = alerts[alertIndex]; - }, 500); + if (alerts.value.length > 0) { + alertIndex = (alertIndex + 1) % alerts.value.length; + currentAlert.value = null; + setTimeout(() => { + currentAlert.value = alerts.value[alertIndex]; + }, 500); + } }, 3500); }; -const setChart = () => { +// 地图数据状态 +const mapData = ref([]); +const scatterData = ref([]); + +// 获取地图数据 +const fetchMapData = async () => { + try { + const response = await mapDataPage1(); + if (response.code === 200) { + mapData.value = response.data; + } else { + console.error('获取地图数据失败:', response.msg); + // 如果接口调用失败,使用默认数据 + mapData.value = [ + { name: 'China', value: 100 }, + { name: 'United States', value: 80 }, + { name: 'Russia', value: 60 }, + { name: 'Brazil', value: 40 }, + { name: 'Canada', value: 30 }, + { name: 'Australia', value: 20 }, + { name: 'India', value: 50 }, + { name: 'Germany', value: 45 }, + { name: 'United Kingdom', value: 40 }, + { name: 'France', value: 35 }, + ]; + } + } catch (error) { + console.error('请求地图数据时发生错误:', error); + // 发生异常时使用默认数据 + mapData.value = [ + { name: 'China', value: 100 }, + { name: 'United States', value: 80 }, + { name: 'Russia', value: 60 }, + { name: 'Brazil', value: 40 }, + { name: 'Canada', value: 30 }, + { name: 'Australia', value: 20 }, + { name: 'India', value: 50 }, + { name: 'Germany', value: 45 }, + { name: 'United Kingdom', value: 40 }, + { name: 'France', value: 35 }, + ]; + } +}; + +const setChart = async () => { if (!chartDiv.value) return; echarts.registerMap('world', worldJson); - const mapData = [ - { name: 'China', value: 100 }, - { name: 'United States', value: 80 }, - { name: 'Russia', value: 60 }, - { name: 'Brazil', value: 40 }, - { name: 'Canada', value: 30 }, - { name: 'Australia', value: 20 }, - { name: 'India', value: 50 }, - { name: 'Germany', value: 45 }, - { name: 'United Kingdom', value: 40 }, - { name: 'France', value: 35 }, - ]; - const scatterData = [ + // 初始化散点数据 + const geoCoordMap = { + 'Beijing': [116.407395, 39.904211], + 'Shanghai': [121.473701, 31.230416], + 'Guangzhou': [113.264385, 23.129112], + 'Chengdu': [104.066541, 30.572269], + 'Washington': [-77.036871, 38.907192], + 'Moscow': [37.6173, 55.7558], + }; + + const scatterDataLocal = [ { name: '北京中心', value: [...geoCoordMap['Beijing'], 100] }, { name: '上海节点', value: [...geoCoordMap['Shanghai'], 80] }, { name: '广州节点', value: [...geoCoordMap['Guangzhou'], 70] }, @@ -186,13 +259,13 @@ const setChart = () => { name: 'Threat Distribution', type: 'map', geoIndex: 0, - data: mapData + data: mapData.value }, { name: 'Key Nodes', type: 'effectScatter', coordinateSystem: 'geo', - data: scatterData, + data: scatterDataLocal, symbolSize: (val) => val[2] / 5, showEffectOn: 'render', rippleEffect: { @@ -222,10 +295,76 @@ const setChart = () => { myChart.setOption(option); }; +// 获取漏洞类型统计数据 +const fetchVulnStats = async () => { + try { + const response = await mapVulnStatsPage1(); + if (response.code === 200) { + // 为每个数据项添加颜色 + const colors = ['#ef4444', '#f97316', '#eab308', '#3b82f6', '#10b981', '#8b5cf6', '#ec4899', '#06b6d4']; + vulnStats.value = response.data.map((item, index) => ({ + ...item, + color: colors[index % colors.length] + })); + } else { + console.error('获取漏洞类型统计数据失败:', response.msg); + // 如果接口调用失败,使用默认数据 + vulnStats.value = [ + { name: 'XSS 跨站脚本', value: 32.1, color: '#ef4444' }, + { name: 'SQL 注入', value: 11.9, color: '#f97316' }, + { name: '内存缓冲区溢出', value: 10.9, color: '#eab308' }, + { name: '输入验证不当', value: 9.3, color: '#3b82f6' }, + { name: '信息泄露', value: 8.0, color: '#10b981' }, + ]; + } + } catch (error) { + console.error('请求漏洞类型统计数据时发生错误:', error); + // 发生异常时使用默认数据 + vulnStats.value = [ + { name: 'XSS 跨站脚本', value: 32.1, color: '#ef4444' }, + { name: 'SQL 注入', value: 11.9, color: '#f97316' }, + { name: '内存缓冲区溢出', value: 10.9, color: '#eab308' }, + { name: '输入验证不当', value: 9.3, color: '#3b82f6' }, + { name: '信息泄露', value: 8.0, color: '#10b981' }, + ]; + } +}; + +// 获取威胁情报地域排行数据 +const fetchRegionStats = async () => { + try { + const response = await mapRegionStatsPage1(); + if (response.code === 200) { + regionStats.value = response.data; + } else { + console.error('获取威胁情报地域排行数据失败:', response.msg); + // 如果接口调用失败,使用默认数据 + regionStats.value = [ + { name: 'United States', score: 435202 }, + { name: 'India', score: 252054 }, + { name: 'China (中国)', score: 184085 }, + { name: 'Brazil', score: 174811 }, + { name: 'Germany', score: 126397 }, + ]; + } + } catch (error) { + console.error('请求威胁情报地域排行数据时发生错误:', error); + // 发生异常时使用默认数据 + regionStats.value = [ + { name: 'United States', score: 435202 }, + { name: 'India', score: 252054 }, + { name: 'China (中国)', score: 184085 }, + { name: 'Brazil', score: 174811 }, + { name: 'Germany', score: 126397 }, + ]; + } +}; + // 生命周期钩子 onMounted(async () => { await nextTick(); // 确保 DOM 已渲染 - setChart(); + await Promise.all([fetchVulnStats(), fetchRegionStats(), fetchMapData(), fetchAlertsData()]); // 同时获取所有统计数据 + await setChart(); // 等待图表设置完成 startAlertRotation(); window.addEventListener('resize', handleResize); }); diff --git a/src/views/Jyh/security/index.vue b/src/views/Jyh/security/index.vue index e6ac874..1b76ad8 100644 --- a/src/views/Jyh/security/index.vue +++ b/src/views/Jyh/security/index.vue @@ -43,23 +43,7 @@ -
-

情报流

- -
-
-
- {{ item.type }} -
- {{ item.content }} -
-
-
-
-
+
@@ -127,6 +111,7 @@ import { usePageResize } from '@/utils/hooks/usePageResize'; import ThreatIntelMap from './components/ThreatIntelMap.vue'; import IndustryDistribution from './components/IndustryDistribution.vue'; import HighRiskComponentRank from './components/HighRiskComponentRank.vue'; +import IntelligenceFeed from './components/IntelligenceFeed.vue'; import StatsGlass from '@/views/Jyh/security/StatsGlass.vue'; // 页面尺寸响应式 @@ -146,33 +131,7 @@ const excellentProjects = ref([ { name: 'NextDenovo', desc: '高效且精确的长读长纠错与组装工具', org: '武汉希望组生物科技有限公司' } ]); -// 情报流数据 -const feedList = ref([ - { - type: 'CISA', - content: 'CISA KEV新增: WordPress 的 OneClick Chat to Order 插件,存在不安全的直接对象引用漏洞 (CVE-2025-13526) ' - }, - { - type: '投毒', - content: '投毒监控: npm 基建级包大规模投毒事件,影响chalk, debug, ansi-styles 等 18 个包' - }, - { - type: 'CNVD', - content: 'CNVD预警: Google Chrome信息泄露漏洞(CNVD-2025-3038304)' - }, - { - type: 'GitHub', - content: 'GitHub安全通告: MovableType 软件的 ContentType 页面 "编辑分类集" 功能存在存储型跨站脚本漏洞' - }, - { - type: '修复', - content: '漏洞修复: Node.js 发布 v20.11.0 版本,修复 3 个高危漏洞' - }, - { - type: '供应链', - content: '供应链预警: Maven 中央仓库发现 12 个伪造开源组件,包含后门程序' - } -]); + // 2. 2025武汉市优秀开源软件项目 const excellentProjects2025 = ref([ @@ -428,82 +387,7 @@ const chartInstances = ref<{ [key: string]: echarts.ECharts | null }>({ CVEChart: null }); -// ===================== 新增:列表自动滚动逻辑 ===================== -// 1. 定义滚动容器的ref -const scrollContainer1 = ref(null); // 第一个列表容器 -const scrollContainer2 = ref(null); // 第二个列表容器 -// 2. 存储定时器(键:1/2,值:定时器ID) -const scrollTimers: { [key: number]: NodeJS.Timeout | null } = { - 1: null, - 2: null -}; - -// 3. 滚动配置 -const scrollStep = 1; // 每次滚动的步长(像素,越小越流畅) -const scrollInterval = 20; // 滚动间隔(毫秒,越小越流畅) - -// 4. 通用自动滚动函数 -const autoScroll = (containerKey: number) => { - // 获取对应容器 - const container = containerKey === 1 ? scrollContainer1.value : scrollContainer2.value; - if (!container) return; - - // 获取列表内容元素(project-list) - const projectList = container.querySelector('.project-list') as HTMLDivElement; - if (!projectList) return; - - // 若内容高度 ≤ 容器高度,不执行滚动 - if (projectList.scrollHeight <= container.clientHeight) { - // 清除现有定时器 - if (scrollTimers[containerKey]) { - clearInterval(scrollTimers[containerKey]!); - scrollTimers[containerKey] = null; - } - return; - } - - // 清除现有定时器(避免重复) - if (scrollTimers[containerKey]) { - clearInterval(scrollTimers[containerKey]!); - } - - // 启动定时器滚动 - scrollTimers[containerKey] = setInterval(() => { - // 向下滚动步长 - container.scrollTop += scrollStep; - - // 滚动到底部时,重置为0(循环滚动) - // 加1是为了兼容像素精度问题 - if (projectList.scrollHeight - container.scrollTop <= container.clientHeight + 1) { - container.scrollTop = 0; // 直接重置(也可以用平滑滚动:container.scrollTo({ top: 0, behavior: 'smooth' })) - } - }, scrollInterval); -}; - -// 5. 暂停滚动 -const pauseScroll = (containerKey: number) => { - if (scrollTimers[containerKey]) { - clearInterval(scrollTimers[containerKey]!); - scrollTimers[containerKey] = null; - } -}; - -// 6. 恢复滚动 -const resumeScroll = (containerKey: number) => { - autoScroll(containerKey); -}; - -// 7. 组件卸载时清除所有定时器 -onUnmounted(() => { - Object.keys(scrollTimers).forEach(key => { - const k = Number(key); - if (scrollTimers[k]) { - clearInterval(scrollTimers[k]!); - scrollTimers[k] = null; - } - }); -}); // CVE视图切换方法 const switchCveView = (view: 'global' | 'regional') => { @@ -1148,18 +1032,12 @@ const initCharts = () => { // 页面resize时重绘图表 watch(widthType, () => { initCharts(); - // 重新启动滚动(防止resize后滚动失效) - autoScroll(1); - autoScroll(2); }); // 挂载时初始化 onMounted(() => { setTimeout(() => { initCharts(); - // 启动列表自动滚动 - autoScroll(1); - autoScroll(2); }, 100); });