Merge remote-tracking branch 'origin/SAP' into SAP
# Conflicts: # src/views/Jyh/security/index.vue
This commit is contained in:
219
src/views/Jyh/security/components/HighRiskComponentRank.vue
Normal file
219
src/views/Jyh/security/components/HighRiskComponentRank.vue
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
<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';
|
||||||
|
|
||||||
|
// 定义属性,允许从父组件传入数据
|
||||||
|
const props = defineProps<{
|
||||||
|
data?: Array<any>;
|
||||||
|
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 initChart = async () => {
|
||||||
|
if (!chartRef.value) return;
|
||||||
|
|
||||||
|
// 销毁之前的实例
|
||||||
|
if (chartInstance) {
|
||||||
|
chartInstance.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
chartInstance = echarts.init(chartRef.value);
|
||||||
|
|
||||||
|
// 高危组件数据(基于真实开源项目使用情况和漏洞统计)
|
||||||
|
const highRiskComponentData = [
|
||||||
|
{ 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 chartData = props.data || highRiskComponentData;
|
||||||
|
|
||||||
|
// 检查是否有有效数据
|
||||||
|
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(() => {
|
||||||
|
initChart();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 监听数据变化,重新渲染图表
|
||||||
|
watch(() => props.data, () => {
|
||||||
|
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>
|
||||||
212
src/views/Jyh/security/components/IndustryDistribution.vue
Normal file
212
src/views/Jyh/security/components/IndustryDistribution.vue
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
<template>
|
||||||
|
<div class="index-module__NV_5cW__chartCard">
|
||||||
|
<h3 class="chartCard__title">行业分布</h3>
|
||||||
|
<div class="chartCard__container">
|
||||||
|
<div class="num-empty" v-if="computedIsEmpty"></div>
|
||||||
|
<div v-else class="industry-distribution-container">
|
||||||
|
<div class="chart-container" ref="chartDiv"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted, onUnmounted, watch, nextTick, computed } from 'vue';
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
// 定义数据类型
|
||||||
|
interface IndustryData {
|
||||||
|
value: number;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定义行业分布数据
|
||||||
|
const industryDistribution: IndustryData[] = [
|
||||||
|
{ value: 25.8, name: '人工智能' },
|
||||||
|
{ value: 18.4, name: '云计算' },
|
||||||
|
{ value: 15.6, name: '大数据' },
|
||||||
|
{ value: 12.2, name: '物联网' },
|
||||||
|
{ value: 9.6, name: '网络安全' },
|
||||||
|
{ value: 7.1, name: '区块链' },
|
||||||
|
{ value: 6.4, name: '工业互联网' },
|
||||||
|
{ value: 4.9, name: '移动开发' }
|
||||||
|
];
|
||||||
|
|
||||||
|
// 定义配色方案
|
||||||
|
const industryColorPalette = [
|
||||||
|
'#00f0ff', '#00b03e', '#ffeb00', '#ffab00',
|
||||||
|
'#ff5100', '#c0232a', '#a262f2', '#379bff'
|
||||||
|
];
|
||||||
|
|
||||||
|
// 内部数据
|
||||||
|
const internalData = ref<IndustryData[]>(industryDistribution);
|
||||||
|
|
||||||
|
// 响应式数据
|
||||||
|
const chartDiv = ref<HTMLDivElement | null>(null);
|
||||||
|
let myChart: echarts.ECharts | null = null;
|
||||||
|
|
||||||
|
// 计算 isEmpty 状态
|
||||||
|
const computedIsEmpty = computed(() => {
|
||||||
|
return internalData.value.length === 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 初始化图表
|
||||||
|
const initChart = () => {
|
||||||
|
if (!chartDiv.value || computedIsEmpty.value) return;
|
||||||
|
|
||||||
|
// 销毁已存在的图表实例
|
||||||
|
if (myChart) {
|
||||||
|
myChart.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
myChart = echarts.init(chartDiv.value);
|
||||||
|
|
||||||
|
const option: echarts.EChartsOption = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'item',
|
||||||
|
formatter: (params: any) => `${params.name}: ${params.percent}% (${params.value}%)`
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
bottom: '0',
|
||||||
|
textStyle: { fontSize: 12 },
|
||||||
|
itemWidth: 10,
|
||||||
|
itemHeight: 10
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '行业分布',
|
||||||
|
type: 'pie',
|
||||||
|
radius: ['40%', '70%'],
|
||||||
|
data: internalData.value,
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
formatter: '{b}: {d}%',
|
||||||
|
fontSize: 10
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
color: (params: any) => {
|
||||||
|
return industryColorPalette[params.dataIndex % industryColorPalette.length];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
grid: { left: 0, bottom: 0, top: 0, right: 0 }
|
||||||
|
};
|
||||||
|
|
||||||
|
myChart.setOption(option);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 响应式处理
|
||||||
|
const handleResize = () => {
|
||||||
|
if (myChart) {
|
||||||
|
myChart.resize();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 生命周期钩子
|
||||||
|
onMounted(() => {
|
||||||
|
nextTick(() => {
|
||||||
|
initChart();
|
||||||
|
});
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
window.removeEventListener('resize', handleResize);
|
||||||
|
if (myChart) {
|
||||||
|
myChart.dispose();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" 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.list-card {
|
||||||
|
padding: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.index-module__NV_5cW__chartCard:hover {
|
||||||
|
border-color: #3b82f64d;
|
||||||
|
transform: translateY(-4px);
|
||||||
|
box-shadow: 0 8px 24px #0000001f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chartCard__wide {
|
||||||
|
grid-column: 1 / -1; /* 跨列显示 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 列表容器样式:隐藏原生滚动条(可选,视觉更美观) */
|
||||||
|
.chartCard__container.list-container {
|
||||||
|
height: auto;
|
||||||
|
max-height: 300px; /* 限制列表高度,超出滚动 */
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 0 0.5rem;
|
||||||
|
/* 隐藏滚动条 */
|
||||||
|
scrollbar-width: none; /* Firefox */
|
||||||
|
-ms-overflow-style: none; /* IE/Edge */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 隐藏webkit内核浏览器的滚动条 */
|
||||||
|
.chartCard__container.list-container::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.num-empty {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.industry-distribution-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
min-height: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 响应式样式 */
|
||||||
|
@media (max-width: 1024px) {
|
||||||
|
.chartCard__container {
|
||||||
|
height: 250px; /* 移动端图表高度调整 */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.chartCard__container {
|
||||||
|
height: 200px; /* 小屏移动端图表高度调整 */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -37,21 +37,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="index-module__NV_5cW__chartCard">
|
<!-- 行业分布 -->
|
||||||
<h3 class="chartCard__title">行业分布</h3>
|
<IndustryDistribution />
|
||||||
<div class="chartCard__container">
|
|
||||||
<div class="num-empty" v-if="isEmpty.university"></div>
|
|
||||||
<div v-else id="cweAttackSurfaceRadar" class="chart"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="index-module__NV_5cW__chartCard">
|
<!-- 高危组件Top 10 -->
|
||||||
<h3 class="chartCard__title">高危组件 Top 10</h3>
|
<HighRiskComponentRank />
|
||||||
<div class="chartCard__container">
|
|
||||||
<div class="num-empty" v-if="isEmpty.community"></div>
|
|
||||||
<div v-else id="highRiskComponentRank" class="chart"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="index-module__NV_5cW__chartCard list-card">
|
<div class="index-module__NV_5cW__chartCard list-card">
|
||||||
<h3 class="chartCard__title">情报流</h3>
|
<h3 class="chartCard__title">情报流</h3>
|
||||||
@@ -135,6 +125,8 @@ import { onMounted, ref, nextTick, watch, onUnmounted } from 'vue';
|
|||||||
import * as echarts from 'echarts';
|
import * as echarts from 'echarts';
|
||||||
import { usePageResize } from '@/utils/hooks/usePageResize';
|
import { usePageResize } from '@/utils/hooks/usePageResize';
|
||||||
import ThreatIntelMap from './components/ThreatIntelMap.vue';
|
import ThreatIntelMap from './components/ThreatIntelMap.vue';
|
||||||
|
import IndustryDistribution from './components/IndustryDistribution.vue';
|
||||||
|
import HighRiskComponentRank from './components/HighRiskComponentRank.vue';
|
||||||
import StatsGlass from '@/views/Jyh/security/StatsGlass.vue';
|
import StatsGlass from '@/views/Jyh/security/StatsGlass.vue';
|
||||||
|
|
||||||
// 页面尺寸响应式
|
// 页面尺寸响应式
|
||||||
@@ -334,20 +326,6 @@ const chartData = ref({
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
// 高危组件数据(基于真实开源项目使用情况和漏洞统计)
|
|
||||||
highRiskComponent: [
|
|
||||||
{ 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和原型污染漏洞' }
|
|
||||||
],
|
|
||||||
|
|
||||||
// 漏洞占比数据
|
// 漏洞占比数据
|
||||||
vulnerabilitySeverity: {
|
vulnerabilitySeverity: {
|
||||||
categories: ['2024Q1', '2024Q2', '2024Q3', '2024Q4', '2025Q1', '2025Q2', '2025Q3', '2025Q4'],
|
categories: ['2024Q1', '2024Q2', '2024Q3', '2024Q4', '2025Q1', '2025Q2', '2025Q3', '2025Q4'],
|
||||||
@@ -379,22 +357,6 @@ const chartData = ref({
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
// 行业分布数据
|
|
||||||
industryDistribution: [
|
|
||||||
{ value: 25.8, name: '人工智能' },
|
|
||||||
{ value: 18.4, name: '云计算' },
|
|
||||||
{ value: 15.6, name: '大数据' },
|
|
||||||
{ value: 12.2, name: '物联网' },
|
|
||||||
{ value: 9.6, name: '网络安全' },
|
|
||||||
{ value: 7.1, name: '区块链' },
|
|
||||||
{ value: 6.4, name: '工业互联网' },
|
|
||||||
{ value: 4.9, name: '移动开发' }
|
|
||||||
],
|
|
||||||
// 科技感配色方案
|
|
||||||
industryColorPalette: [
|
|
||||||
'#00f0ff', '#00b03e', '#ffeb00', '#ffab00',
|
|
||||||
'#ff5100', '#c0232a', '#a262f2', '#379bff'
|
|
||||||
],
|
|
||||||
// CVE图表数据
|
// CVE图表数据
|
||||||
cveData: {
|
cveData: {
|
||||||
// 全球CVE:2024年实际数据(基于Excel数据)
|
// 全球CVE:2024年实际数据(基于Excel数据)
|
||||||
@@ -451,8 +413,7 @@ const isEmpty = ref({
|
|||||||
ossCompass: false,
|
ossCompass: false,
|
||||||
developer: false,
|
developer: false,
|
||||||
languageTech: false,
|
languageTech: false,
|
||||||
highRiskComponent: false,
|
|
||||||
industryDistribution: false,
|
|
||||||
cveData: false
|
cveData: false
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -463,7 +424,6 @@ const chartInstances = ref<{ [key: string]: echarts.ECharts | null }>({
|
|||||||
ossCompassChart: null,
|
ossCompassChart: null,
|
||||||
developerChart: null,
|
developerChart: null,
|
||||||
languageTechChart: null,
|
languageTechChart: null,
|
||||||
highRiskComponentRank: null,
|
|
||||||
cweAttackSurfaceRadar: null,
|
cweAttackSurfaceRadar: null,
|
||||||
CVEChart: null
|
CVEChart: null
|
||||||
});
|
});
|
||||||
@@ -890,122 +850,9 @@ const initDeveloperChart = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 初始化行业分布图表(饼图)
|
|
||||||
const initIndustryDistributionChart = () => {
|
|
||||||
const el = document.getElementById('cweAttackSurfaceRadar');
|
|
||||||
if (!el) return;
|
|
||||||
|
|
||||||
if (chartInstances.value.cweAttackSurfaceRadar) {
|
|
||||||
chartInstances.value.cweAttackSurfaceRadar.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
const myChart = echarts.init(el);
|
|
||||||
chartInstances.value.cweAttackSurfaceRadar = myChart;
|
|
||||||
|
|
||||||
myChart.setOption({
|
|
||||||
tooltip: {
|
|
||||||
trigger: 'item',
|
|
||||||
formatter: (params: any) => `${params.name}: ${params.percent}% (${params.value}%)`
|
|
||||||
},
|
|
||||||
legend: {
|
|
||||||
bottom: '0',
|
|
||||||
textStyle: { fontSize: 12 },
|
|
||||||
itemWidth: 10,
|
|
||||||
itemHeight: 10
|
|
||||||
},
|
|
||||||
color: chartData.value.industryColorPalette,
|
|
||||||
series: [
|
|
||||||
{
|
|
||||||
name: '行业分布',
|
|
||||||
type: 'pie',
|
|
||||||
radius: ['40%', '70%'],
|
|
||||||
data: chartData.value.industryDistribution,
|
|
||||||
label: {
|
|
||||||
show: true,
|
|
||||||
formatter: '{b}: {d}%',
|
|
||||||
fontSize: 10
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
grid: { left: 0, bottom: 0, top: 0, right: 0 }
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// 初始化高危组件Top 10图表(横向柱状图)
|
|
||||||
const initHighRiskComponentChart = () => {
|
|
||||||
const el = document.getElementById('highRiskComponentRank');
|
|
||||||
if (!el) return;
|
|
||||||
|
|
||||||
if (chartInstances.value.highRiskComponentRank) {
|
|
||||||
chartInstances.value.highRiskComponentRank.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
const myChart = echarts.init(el);
|
|
||||||
chartInstances.value.highRiskComponentRank = myChart;
|
|
||||||
|
|
||||||
// 提取组件名称和CVSS评分
|
|
||||||
const yAxisData = chartData.value.highRiskComponent.map(item => item.name);
|
|
||||||
const seriesData = chartData.value.highRiskComponent.map(item => ({
|
|
||||||
value: item.cvss,
|
|
||||||
vulnCount: item.vulnCount,
|
|
||||||
usage: item.usage,
|
|
||||||
desc: item.desc
|
|
||||||
}));
|
|
||||||
|
|
||||||
myChart.setOption({
|
|
||||||
tooltip: {
|
|
||||||
trigger: 'axis',
|
|
||||||
axisPointer: { type: 'shadow' },
|
|
||||||
formatter: (params: any) => {
|
|
||||||
const data = chartData.value.highRiskComponent[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: yAxisData,
|
|
||||||
axisLabel: { fontSize: 12 }
|
|
||||||
},
|
|
||||||
series: [
|
|
||||||
{
|
|
||||||
name: 'CVSS评分',
|
|
||||||
type: 'bar',
|
|
||||||
data: seriesData,
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// 初始化编程语言使用趋势堆叠面积图
|
// 初始化编程语言使用趋势堆叠面积图
|
||||||
const initLanguageTrendChart = () => {
|
const initLanguageTrendChart = () => {
|
||||||
@@ -1275,8 +1122,8 @@ const initCharts = () => {
|
|||||||
ossCompass: chartData.value.ossCompass.data.every(item => item === 0),
|
ossCompass: chartData.value.ossCompass.data.every(item => item === 0),
|
||||||
developer: chartData.value.developer.data.every(item => item === 0),
|
developer: chartData.value.developer.data.every(item => item === 0),
|
||||||
languageTech: chartData.value.languageTech.series.every(s => s.data.every(d => d === 0)),
|
languageTech: chartData.value.languageTech.series.every(s => s.data.every(d => d === 0)),
|
||||||
highRiskComponent: chartData.value.highRiskComponent.length === 0,
|
|
||||||
industryDistribution: chartData.value.industryDistribution.length === 0,
|
|
||||||
cveData: chartData.value.cveData.global.cveCount.every(item => item === 0) &&
|
cveData: chartData.value.cveData.global.cveCount.every(item => item === 0) &&
|
||||||
chartData.value.cveData.regional.cveCount.every(item => item === 0),
|
chartData.value.cveData.regional.cveCount.every(item => item === 0),
|
||||||
languageTrend: chartData.value.languageTrend.series.every(s => s.data.every(d => d === 0)),
|
languageTrend: chartData.value.languageTrend.series.every(s => s.data.every(d => d === 0)),
|
||||||
@@ -1290,8 +1137,8 @@ const initCharts = () => {
|
|||||||
if (!isEmpty.value.ossCompass) nextTick(() => initOssCompassChart());
|
if (!isEmpty.value.ossCompass) nextTick(() => initOssCompassChart());
|
||||||
if (!isEmpty.value.developer) nextTick(() => initDeveloperChart());
|
if (!isEmpty.value.developer) nextTick(() => initDeveloperChart());
|
||||||
if (!isEmpty.value.languageTech) nextTick(() => initLanguageTechChart());
|
if (!isEmpty.value.languageTech) nextTick(() => initLanguageTechChart());
|
||||||
if (!isEmpty.value.highRiskComponent) nextTick(() => initHighRiskComponentChart());
|
|
||||||
if (!isEmpty.value.industryDistribution) nextTick(() => initIndustryDistributionChart());
|
|
||||||
if (!isEmpty.value.cveData) nextTick(() => initCVEChart());
|
if (!isEmpty.value.cveData) nextTick(() => initCVEChart());
|
||||||
if (!isEmpty.value.languageTrend) nextTick(() => initLanguageTrendChart());
|
if (!isEmpty.value.languageTrend) nextTick(() => initLanguageTrendChart());
|
||||||
if (!isEmpty.value.vulnerabilitySeverity) nextTick(() => initVulnerabilitySeverityChart());
|
if (!isEmpty.value.vulnerabilitySeverity) nextTick(() => initVulnerabilitySeverityChart());
|
||||||
|
|||||||
Reference in New Issue
Block a user