Tcode平台改造升级-CVE 月度趋势图表
This commit is contained in:
@@ -160,13 +160,40 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 7. 武汉市语言与技术竞争趋势(折线图,近五年)-->
|
<!-- 7. 武汉市语言与技术竞争趋势(折线图,近五年)
|
||||||
<div class="index-module__NV_5cW__chartCard chartCard__wide">
|
<div class="index-module__NV_5cW__chartCard chartCard__wide">
|
||||||
<h3 class="chartCard__title">武汉市语言与技术竞争趋势(2021-2025)</h3>
|
<h3 class="chartCard__title">武汉市语言与技术竞争趋势(2021-2025)</h3>
|
||||||
<div class="chartCard__container">
|
<div class="chartCard__container">
|
||||||
<div class="num-empty" v-if="isEmpty.languageTech"></div>
|
<div class="num-empty" v-if="isEmpty.languageTech"></div>
|
||||||
<div v-else id="languageTechChart" class="chart"></div>
|
<div v-else id="languageTechChart" class="chart"></div>
|
||||||
</div>
|
</div>
|
||||||
|
</div> -->
|
||||||
|
|
||||||
|
<div class="index-module__NV_5cW__chartCard chartCard__wide">
|
||||||
|
<div class="chartCard__header">
|
||||||
|
<h3 class="chartCard__title">CVE 月度趋势</h3>
|
||||||
|
<!-- 视图切换按钮,用于切换全球和区域视图 -->
|
||||||
|
<div class="chartCard__actions">
|
||||||
|
<button
|
||||||
|
class="chartCard__actionBtn"
|
||||||
|
:class="{ 'chartCard__actionBtn--active': cveView === 'global' }"
|
||||||
|
@click="switchCveView('global')"
|
||||||
|
>
|
||||||
|
全球
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="chartCard__actionBtn"
|
||||||
|
:class="{ 'chartCard__actionBtn--active': cveView === 'regional' }"
|
||||||
|
@click="switchCveView('regional')"
|
||||||
|
>
|
||||||
|
区域
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="chartCard__container">
|
||||||
|
<div class="num-empty" v-if="isEmpty.cveData"></div>
|
||||||
|
<div v-else id="CVEChart" class="chart"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -374,9 +401,25 @@ const chartData = ref({
|
|||||||
industryColorPalette: [
|
industryColorPalette: [
|
||||||
'#00f0ff', '#00b03e', '#ffeb00', '#ffab00',
|
'#00f0ff', '#00b03e', '#ffeb00', '#ffab00',
|
||||||
'#ff5100', '#c0232a', '#a262f2', '#379bff'
|
'#ff5100', '#c0232a', '#a262f2', '#379bff'
|
||||||
]
|
],
|
||||||
|
// CVE图表数据
|
||||||
|
cveData: {
|
||||||
|
// 全球CVE:2024年实际数据(基于Excel数据)
|
||||||
|
global: {
|
||||||
|
cveCount: [98402, 418, 7025, 3711, 5115, 3228, 3044, 2957, 2592, 1455, 4826, 4661],
|
||||||
|
acceleration: [0, -99.58, 1580.62, -47.17, 37.83, -36.89, -5.70, -2.86, -12.34, -43.87, 231.68, -3.42] // 环比增长率(%)
|
||||||
|
},
|
||||||
|
// 中国区域CVE:2024年数据(约占全球30%)
|
||||||
|
regional: {
|
||||||
|
cveCount: [29521, 125, 2108, 1113, 1535, 968, 913, 887, 778, 437, 1448, 1398],
|
||||||
|
acceleration: [0, -99.58, 1580.62, -47.17, 37.83, -36.89, -5.70, -2.86, -12.34, -43.87, 231.68, -3.42] // 环比增长率(%)
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 视图切换状态变量(global: 全球, regional: 区域)
|
||||||
|
const cveView = ref('global');
|
||||||
|
|
||||||
// 空数据标识
|
// 空数据标识
|
||||||
const isEmpty = ref({
|
const isEmpty = ref({
|
||||||
university: false,
|
university: false,
|
||||||
@@ -385,7 +428,8 @@ const isEmpty = ref({
|
|||||||
developer: false,
|
developer: false,
|
||||||
languageTech: false,
|
languageTech: false,
|
||||||
highRiskComponent: false,
|
highRiskComponent: false,
|
||||||
industryDistribution: false
|
industryDistribution: false,
|
||||||
|
cveData: false
|
||||||
});
|
});
|
||||||
|
|
||||||
// 图表实例存储(用于resize时销毁重绘)
|
// 图表实例存储(用于resize时销毁重绘)
|
||||||
@@ -396,7 +440,8 @@ const chartInstances = ref<{ [key: string]: echarts.ECharts | null }>({
|
|||||||
developerChart: null,
|
developerChart: null,
|
||||||
languageTechChart: null,
|
languageTechChart: null,
|
||||||
highRiskComponentRank: null,
|
highRiskComponentRank: null,
|
||||||
cweAttackSurfaceRadar: null
|
cweAttackSurfaceRadar: null,
|
||||||
|
CVEChart: null
|
||||||
});
|
});
|
||||||
|
|
||||||
// ===================== 新增:列表自动滚动逻辑 =====================
|
// ===================== 新增:列表自动滚动逻辑 =====================
|
||||||
@@ -475,6 +520,145 @@ onUnmounted(() => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// CVE视图切换方法
|
||||||
|
const switchCveView = (view: 'global' | 'regional') => {
|
||||||
|
cveView.value = view;
|
||||||
|
nextTick(() => {
|
||||||
|
initCVEChart();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 初始化CVE图表(柱状图+折线图)
|
||||||
|
const initCVEChart = () => {
|
||||||
|
const el = document.getElementById('CVEChart');
|
||||||
|
if (!el) return;
|
||||||
|
|
||||||
|
if (chartInstances.value.CVEChart) {
|
||||||
|
chartInstances.value.CVEChart.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
const myChart = echarts.init(el);
|
||||||
|
chartInstances.value.CVEChart = myChart;
|
||||||
|
|
||||||
|
// 获取当前视图的数据
|
||||||
|
const currentData = chartData.value.cveData[cveView.value === 'global' ? 'global' : 'regional'];
|
||||||
|
const cveCount = currentData.cveCount;
|
||||||
|
const acceleration = currentData.acceleration;
|
||||||
|
|
||||||
|
// 月份数据
|
||||||
|
const months = ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'];
|
||||||
|
|
||||||
|
myChart.setOption({
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'cross',
|
||||||
|
crossStyle: {
|
||||||
|
color: '#999'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
formatter: (params: any) => {
|
||||||
|
let res = `<div>${params[0].name}</div>`;
|
||||||
|
params.forEach((p: any) => {
|
||||||
|
if (p.seriesName === 'CVE数量') {
|
||||||
|
res += `<div>${p.marker}${p.seriesName}: ${p.value}</div>`;
|
||||||
|
} else {
|
||||||
|
res += `<div>${p.marker}${p.seriesName}: ${p.value}%</div>`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
top: 0,
|
||||||
|
textStyle: { fontSize: 12 },
|
||||||
|
data: ['CVE数量', '环比增长率']
|
||||||
|
},
|
||||||
|
grid: { left: '3%', right: '4%', bottom: '3%', top: '15%', containLabel: true },
|
||||||
|
xAxis: [
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
data: months,
|
||||||
|
axisPointer: {
|
||||||
|
type: 'shadow'
|
||||||
|
},
|
||||||
|
axisLabel: { fontSize: 12 }
|
||||||
|
}
|
||||||
|
],
|
||||||
|
yAxis: [
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
name: 'CVE数量',
|
||||||
|
nameTextStyle: { fontSize: 12, color: '#333' },
|
||||||
|
axisLabel: { fontSize: 12, color: '#666' },
|
||||||
|
position: 'left',
|
||||||
|
splitLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: {
|
||||||
|
type: 'dashed'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
name: '环比增长率(%)',
|
||||||
|
nameTextStyle: { fontSize: 12, color: '#333' },
|
||||||
|
axisLabel: { fontSize: 12, color: '#666' },
|
||||||
|
position: 'right',
|
||||||
|
splitLine: {
|
||||||
|
show: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: 'CVE数量',
|
||||||
|
type: 'bar',
|
||||||
|
data: cveCount,
|
||||||
|
itemStyle: {
|
||||||
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||||
|
{ offset: 0, color: '#3b82f6' },
|
||||||
|
{ offset: 1, color: '#60a5fa' }
|
||||||
|
])
|
||||||
|
},
|
||||||
|
barWidth: 20,
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'top',
|
||||||
|
formatter: '{c}',
|
||||||
|
fontSize: 10,
|
||||||
|
color: '#333'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '环比增长率',
|
||||||
|
type: 'line',
|
||||||
|
yAxisIndex: 1,
|
||||||
|
data: acceleration,
|
||||||
|
itemStyle: {
|
||||||
|
color: '#ef4444',
|
||||||
|
borderWidth: 2
|
||||||
|
},
|
||||||
|
lineStyle: {
|
||||||
|
width: 2,
|
||||||
|
color: '#ef4444'
|
||||||
|
},
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 6,
|
||||||
|
smooth: true,
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'top',
|
||||||
|
formatter: (params: any) => `${params.value}%`,
|
||||||
|
fontSize: 10,
|
||||||
|
color: '#333'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// ===================== 新增结束 =====================
|
// ===================== 新增结束 =====================
|
||||||
|
|
||||||
// 初始化高校参与分布饼图
|
// 初始化高校参与分布饼图
|
||||||
@@ -870,7 +1054,9 @@ const initCharts = () => {
|
|||||||
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,
|
highRiskComponent: chartData.value.highRiskComponent.length === 0,
|
||||||
industryDistribution: chartData.value.industryDistribution.length === 0
|
industryDistribution: chartData.value.industryDistribution.length === 0,
|
||||||
|
cveData: chartData.value.cveData.global.cveCount.every(item => item === 0) &&
|
||||||
|
chartData.value.cveData.regional.cveCount.every(item => item === 0)
|
||||||
};
|
};
|
||||||
|
|
||||||
// 初始化各图表
|
// 初始化各图表
|
||||||
@@ -881,6 +1067,7 @@ const initCharts = () => {
|
|||||||
if (!isEmpty.value.languageTech) nextTick(() => initLanguageTechChart());
|
if (!isEmpty.value.languageTech) nextTick(() => initLanguageTechChart());
|
||||||
if (!isEmpty.value.highRiskComponent) nextTick(() => initHighRiskComponentChart());
|
if (!isEmpty.value.highRiskComponent) nextTick(() => initHighRiskComponentChart());
|
||||||
if (!isEmpty.value.industryDistribution) nextTick(() => initIndustryDistributionChart());
|
if (!isEmpty.value.industryDistribution) nextTick(() => initIndustryDistributionChart());
|
||||||
|
if (!isEmpty.value.cveData) nextTick(() => initCVEChart());
|
||||||
};
|
};
|
||||||
|
|
||||||
// 页面resize时重绘图表
|
// 页面resize时重绘图表
|
||||||
@@ -1489,4 +1676,42 @@ onMounted(() => {
|
|||||||
max-height: 300px;
|
max-height: 300px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 图表卡片头部样式 */
|
||||||
|
.chartCard__header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 图表操作按钮样式 */
|
||||||
|
.chartCard__actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chartCard__actionBtn {
|
||||||
|
padding: 4px 12px;
|
||||||
|
border: 1px solid #d1d5db;
|
||||||
|
background-color: #f9fafb;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chartCard__actionBtn:hover {
|
||||||
|
background-color: #f3f4f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chartCard__actionBtn--active {
|
||||||
|
background-color: #3b82f6;
|
||||||
|
color: white;
|
||||||
|
border-color: #3b82f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chartCard__actionBtn--active:hover {
|
||||||
|
background-color: #2563eb;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
Reference in New Issue
Block a user