Files
Situation-Awareness-Platfor…/src/views/Jyh/security/components/HighRiskComponentRank.vue

265 lines
8.1 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="index-module__NV_5cW__chartCard">
<h3 class="chartCard__title">高危组件 Top 10</h3>
<div class="chartCard__container">
<div class="num-empty" v-if="isEmpty"></div>
<div v-else ref="chartRef" class="chart"></div>
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref, nextTick, watch, onUnmounted } from 'vue';
import * as echarts from 'echarts';
import { highRiskComponentPage1 } from '@/api/jyh/situation';
// 定义组件数据结构
interface ComponentData {
name: string;
cvss: number;
vulnCount: number;
usage: string;
desc: string;
}
// 定义属性,允许从父组件传入数据
const props = defineProps<{
data?: Array<ComponentData>;
loading?: boolean;
}>();
// 定义 emits 事件
const emit = defineEmits<{
(e: 'chart-ready', chart: echarts.ECharts): void;
}>();
// 空数据标识
const isEmpty = ref(false);
// 图表DOM引用
const chartRef = ref<HTMLDivElement | null>(null);
// 图表实例存储
let chartInstance: echarts.ECharts | null = null;
// 组件数据
const componentData = ref<ComponentData[]>([]);
// 获取高危组件数据
const fetchComponentData = async () => {
try {
const response = await highRiskComponentPage1();
if (response.data.code === 200) {
componentData.value = response.data.data;
} else {
console.error('获取高危组件数据失败:', response.data.msg || '未知错误');
// 如果接口调用失败,使用默认数据
componentData.value = [
{ name: 'Log4j2', cvss: 9.8, vulnCount: 15, usage: '1200万+', desc: 'CVE-2021-44228等严重RCE漏洞' },
{ name: 'Apache Struts2', cvss: 9.7, vulnCount: 12, usage: '850万+', desc: 'S2系列远程代码执行漏洞' },
{ name: 'OpenSSL', cvss: 9.6, vulnCount: 18, usage: '2500万+', desc: '心脏出血、幽灵猫等高危漏洞' },
{ name: 'Spring Framework', cvss: 9.5, vulnCount: 9, usage: '1800万+', desc: 'Spring4Shell等RCE漏洞' },
{ name: 'Jackson', cvss: 9.4, vulnCount: 11, usage: '1000万+', desc: '反序列化漏洞影响Java生态' },
{ name: 'Fastjson', cvss: 9.3, vulnCount: 14, usage: '600万+', desc: '多个反序列化RCE漏洞' },
{ name: 'Apache Commons', cvss: 9.2, vulnCount: 8, usage: '1500万+', desc: 'Collections反序列化漏洞' },
{ name: 'jQuery', cvss: 9.1, vulnCount: 6, usage: '3000万+', desc: 'XSS和原型污染漏洞' },
{ name: 'Lodash', cvss: 9.0, vulnCount: 5, usage: '2200万+', desc: '原型污染漏洞影响Node.js生态' },
{ name: 'Moment.js', cvss: 9.0, vulnCount: 4, usage: '1100万+', desc: 'ReDoS和原型污染漏洞' }
];
}
} catch (error) {
console.error('请求高危组件数据时发生错误:', error);
// 发生异常时使用默认数据
componentData.value = [
{ name: 'Log4j2', cvss: 9.8, vulnCount: 15, usage: '1200万+', desc: 'CVE-2021-44228等严重RCE漏洞' },
{ name: 'Apache Struts2', cvss: 9.7, vulnCount: 12, usage: '850万+', desc: 'S2系列远程代码执行漏洞' },
{ name: 'OpenSSL', cvss: 9.6, vulnCount: 18, usage: '2500万+', desc: '心脏出血、幽灵猫等高危漏洞' },
{ name: 'Spring Framework', cvss: 9.5, vulnCount: 9, usage: '1800万+', desc: 'Spring4Shell等RCE漏洞' },
{ name: 'Jackson', cvss: 9.4, vulnCount: 11, usage: '1000万+', desc: '反序列化漏洞影响Java生态' },
{ name: 'Fastjson', cvss: 9.3, vulnCount: 14, usage: '600万+', desc: '多个反序列化RCE漏洞' },
{ name: 'Apache Commons', cvss: 9.2, vulnCount: 8, usage: '1500万+', desc: 'Collections反序列化漏洞' },
{ name: 'jQuery', cvss: 9.1, vulnCount: 6, usage: '3000万+', desc: 'XSS和原型污染漏洞' },
{ name: 'Lodash', cvss: 9.0, vulnCount: 5, usage: '2200万+', desc: '原型污染漏洞影响Node.js生态' },
{ name: 'Moment.js', cvss: 9.0, vulnCount: 4, usage: '1100万+', desc: 'ReDoS和原型污染漏洞' }
];
}
};
// 初始化图表
const initChart = async () => {
if (!chartRef.value) return;
// 销毁之前的实例
if (chartInstance) {
chartInstance.dispose();
}
chartInstance = echarts.init(chartRef.value);
// 如果有传入的数据,则优先使用传入的数据,否则使用获取到的数据
const chartData = props.data || componentData.value;
// 检查是否有有效数据
isEmpty.value = !chartData || chartData.length === 0;
if (isEmpty.value) {
return;
}
// 准备图表数据
const componentNames = chartData.map(item => item.name);
const cvssScores = chartData.map(item => item.cvss);
const vulnCounts = chartData.map(item => item.vulnCount);
const usages = chartData.map(item => item.usage);
const descs = chartData.map(item => item.desc);
// 配置图表选项,与原页面保持一致
const option = {
tooltip: {
trigger: 'axis',
axisPointer: { type: 'shadow' },
formatter: (params: any) => {
const data = chartData[params[0].dataIndex];
return `
<div style="padding: 4px;">
<div style="font-weight: bold; font-size: 14px;">${data.name}</div>
<div>CVSS评分: ${data.cvss}</div>
<div>漏洞数量: ${data.vulnCount}个</div>
<div>使用量: ${data.usage}</div>
<div>漏洞描述: ${data.desc}</div>
</div>
`;
}
},
grid: { left: '15%', right: '4%', bottom: '3%', top: '3%', containLabel: true },
xAxis: {
type: 'value',
name: 'CVSS评分',
nameTextStyle: { fontSize: 12 },
axisLabel: { fontSize: 12 },
max: 10
},
yAxis: {
type: 'category',
data: componentNames,
axisLabel: { fontSize: 12 },
inverse: true // 反转Y轴让最高的值在顶部
},
series: [
{
name: 'CVSS评分',
type: 'bar',
data: cvssScores,
itemStyle: {
color: (params: any) => {
const value = params.value;
if (value >= 9.0) return '#ef4444'; // 红色 - 严重
if (value >= 7.0) return '#f97316'; // 橙色 - 高危
if (value >= 4.0) return '#eab308'; // 黄色 - 中危
return '#22c55e'; // 绿色 - 低危
}
},
label: {
show: true,
position: 'right',
formatter: '{c}',
fontSize: 12
}
}
]
};
chartInstance.setOption(option);
// 触发图表准备就绪事件
emit('chart-ready', chartInstance);
};
// 组件挂载时初始化图表
onMounted(async () => {
await fetchComponentData(); // 获取高危组件数据
await nextTick();
initChart();
});
// 监听数据变化,重新渲染图表
watch(() => props.data, () => {
initChart();
}, { deep: true });
// 监听组件内部数据变化,重新渲染图表
watch(componentData, () => {
initChart();
}, { deep: true });
// 组件卸载时清理图表实例
onUnmounted(() => {
if (chartInstance) {
chartInstance.dispose();
chartInstance = null;
}
});
// 页面大小变化时调整图表大小
const resizeHandler = () => {
if (chartInstance) {
chartInstance.resize();
}
};
// 监听窗口大小变化
window.addEventListener('resize', resizeHandler);
// 在组件卸载时移除事件监听
onUnmounted(() => {
window.removeEventListener('resize', resizeHandler);
});
</script>
<style scoped>
.index-module__NV_5cW__chartCard {
-webkit-backdrop-filter: blur(10px);
backdrop-filter: blur(10px);
background: #ffffffe6;
border: 1px solid #ffffff4d;
border-radius: 1rem;
padding: 1.5rem;
box-shadow: 0 4px 16px #00000014;
transition: all 0.3s;
}
.index-module__NV_5cW__chartCard:hover {
border-color: #3b82f64d;
transform: translateY(-4px);
box-shadow: 0 8px 24px #0000001f;
}
.chartCard__title {
color: #333;
font-size: 1.1rem;
font-weight: 600;
margin: 0 0 1rem 0;
text-align: left;
}
.chartCard__container {
width: 100%;
height: 300px; /* 图表高度 */
position: relative;
}
.chart {
width: 100%;
height: 100%;
}
.num-empty {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
color: #999;
font-size: 14px;
}
</style>