Tcode运营平台改造升级-全球开源风险态势感知监控中心世界地图修改
This commit is contained in:
@@ -10,7 +10,6 @@
|
||||
<div class="index-module__NV_5cW__titleSection">
|
||||
<div class="index-module__NV_5cW__titleContainer">
|
||||
<h1 class="index-module__NV_5cW__title">开源生态与贡献价值全景</h1>
|
||||
<div class="index-module__NV_5cW__titleBadge">WOS</div>
|
||||
</div>
|
||||
<!-- <div class="index-module__NV_5cW__subtitle">-->
|
||||
<!-- <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none"-->
|
||||
@@ -2793,7 +2792,7 @@ onMounted(() => {
|
||||
|
||||
.chartCard__container {
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
height: 390px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
@@ -2808,7 +2807,7 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
/* 列表容器样式:隐藏原生滚动条(可选,视觉更美观) */
|
||||
.chartCard__container.list-container {
|
||||
.list-container {
|
||||
height: auto;
|
||||
max-height: 300px;
|
||||
/* 限制列表高度,超出滚动 */
|
||||
@@ -3136,7 +3135,8 @@ onMounted(() => {
|
||||
|
||||
/* 明星开发者特殊样式 */
|
||||
.developer-list-container {
|
||||
max-height: 320px;
|
||||
max-height: 400px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.developer-item {
|
||||
@@ -3349,9 +3349,6 @@ onMounted(() => {
|
||||
|
||||
/* 地图区域样式(科技蓝风格) */
|
||||
.map-section {
|
||||
background: linear-gradient(135deg,
|
||||
rgba(0, 30, 80, 0.08) 0%,
|
||||
rgba(0, 63, 208, 0.08) 100%);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border: 2px solid rgba(0, 192, 255, 0.1);
|
||||
@@ -3392,9 +3389,6 @@ onMounted(() => {
|
||||
|
||||
.map-rank-panel {
|
||||
width: 300px;
|
||||
background: linear-gradient(135deg,
|
||||
rgba(15, 23, 42, 0.9) 0%,
|
||||
rgba(0, 30, 80, 0.9) 100%);
|
||||
border-radius: 1rem;
|
||||
border: 2px solid rgba(0, 192, 255, 0.3);
|
||||
padding: 1.5rem;
|
||||
@@ -3409,7 +3403,7 @@ onMounted(() => {
|
||||
.panel-title {
|
||||
font-size: 1.2rem;
|
||||
font-weight: 700;
|
||||
color: #00F2FF;
|
||||
color: black;
|
||||
text-align: center;
|
||||
padding-bottom: 1rem;
|
||||
margin-bottom: 0.5rem;
|
||||
@@ -3509,7 +3503,7 @@ onMounted(() => {
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #e2e8f0;
|
||||
//color: #e2e8f0;
|
||||
}
|
||||
|
||||
.rank-score {
|
||||
@@ -3522,10 +3516,6 @@ onMounted(() => {
|
||||
|
||||
/* 热力图特殊样式 */
|
||||
.heatmap-section {
|
||||
background: linear-gradient(135deg,
|
||||
rgba(59, 130, 246, 0.03) 0%,
|
||||
rgba(139, 92, 246, 0.03) 50%,
|
||||
rgba(236, 72, 153, 0.03) 100%);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -1,295 +1,284 @@
|
||||
<template>
|
||||
<div class="threat-intel-map">
|
||||
<div ref="chartDiv" class="map-chart"></div>
|
||||
在 Vue 3 项目中,直接搬运 Vue 2 代码不生效,主要原因有三点:生命周期钩子的变化、Ref 获取方式的变化、以及 全局变量挂载时机。
|
||||
|
||||
<!-- 左侧漏洞类型面板 -->
|
||||
<div class="info-panel panel-left">
|
||||
<div class="panel-title">🔍 漏洞类型分布</div>
|
||||
<div class="vuln-list">
|
||||
<div class="vuln-item" v-for="(item, index) in vulnTypes" :key="index">
|
||||
<div class="vuln-header">
|
||||
<span class="vuln-name">{{ item.name }}</span>
|
||||
<span class="vuln-count">{{ item.count }}</span>
|
||||
以下是针对 Vue 3 组合式 API (Composition API) 优化后的代码,建议直接替换:
|
||||
|
||||
核心修改说明:
|
||||
ref 声明:Vue 3 需要使用 const chartDiv = ref(null) 来声明模板引用。
|
||||
|
||||
生命周期:mounted 变为 onMounted,beforeDestroy 变为 onBeforeUnmount。
|
||||
|
||||
挂载时机:确保在 import world.js 之前,window.echarts 已经被赋值,否则 world.js 执行时依然会因为找不到 echarts 而报错。
|
||||
|
||||
Vue 3 适配版代码
|
||||
代码段
|
||||
|
||||
<template>
|
||||
<div class="dashboard-container">
|
||||
<div ref="chartDiv" class="global-map"></div>
|
||||
|
||||
<div class="panel panel-left">
|
||||
<div class="panel-title">🛡️ 漏洞类型分布 (Top 5)</div>
|
||||
<div class="stat-list">
|
||||
<div class="stat-item" v-for="(item, index) in vulnStats" :key="index">
|
||||
<div class="stat-label">
|
||||
<span>{{ item.name }}</span>
|
||||
<span class="stat-value">{{ item.value }}%</span>
|
||||
</div>
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill" :style="{ width: item.percentage + '%', background: item.color }"></div>
|
||||
</div>
|
||||
<div class="vuln-trend" :class="item.trend">
|
||||
{{ item.trend === 'up' ? '↗' : item.trend === 'down' ? '↘' : '→' }} {{ item.change }}%
|
||||
<div class="progress-fill" :style="{ width: item.value + '%', background: item.color }"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧威胁情报排行 -->
|
||||
<div class="info-panel panel-right">
|
||||
<div class="panel-title">📊 威胁情报排行</div>
|
||||
<div class="intel-ranking">
|
||||
<div class="rank-item" v-for="(item, index) in intelRanking" :key="index">
|
||||
<div class="rank-badge" :class="'rank-' + (index + 1)">{{ index + 1 }}</div>
|
||||
<div class="rank-info">
|
||||
<div class="rank-country">{{ item.country }}</div>
|
||||
<div class="rank-threats">{{ item.threats.toLocaleString() }} 威胁</div>
|
||||
</div>
|
||||
<div class="rank-indicator" :style="{ background: item.riskLevel === 'high' ? '#ef4444' : item.riskLevel === 'medium' ? '#f59e0b' : '#10b981' }"></div>
|
||||
<div class="panel panel-right">
|
||||
<div class="panel-title">🌍 威胁情报地域排行</div>
|
||||
<div class="rank-list">
|
||||
<div class="rank-item" v-for="(item, index) in regionStats" :key="index">
|
||||
<span class="rank-num" :class="'top-' + (index + 1)">{{ index + 1 }}</span>
|
||||
<span class="rank-name">{{ item.name }}</span>
|
||||
<span class="rank-score">{{ item.score.toLocaleString() }} 次</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<transition name="fade-slide">
|
||||
<div v-if="currentAlert" class="alert-card">
|
||||
<div class="alert-header">
|
||||
<span class="alert-icon">⚠️</span> 实时情报监测
|
||||
</div>
|
||||
<div class="alert-content">
|
||||
<div class="alert-title">{{ currentAlert.title }}</div>
|
||||
<div class="alert-desc">{{ currentAlert.desc }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
import * as echarts from 'echarts'
|
||||
import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue';
|
||||
import * as echarts from 'echarts';
|
||||
import worldJson from './world.json';
|
||||
|
||||
// 响应式数据
|
||||
const chartDiv = ref(null)
|
||||
const myChart = ref(null)
|
||||
const chartDiv = ref(null);
|
||||
let myChart = null;
|
||||
let timer = null;
|
||||
|
||||
// 漏洞类型数据
|
||||
const vulnTypes = ref([
|
||||
{ name: 'XSS 跨站脚本', count: 1247, percentage: 35, color: '#ef4444', trend: 'up', change: 12 },
|
||||
{ name: 'SQL 注入', count: 892, percentage: 25, color: '#f97316', trend: 'down', change: 8 },
|
||||
{ name: '缓冲区溢出', count: 634, percentage: 18, color: '#eab308', trend: 'up', change: 15 },
|
||||
{ name: '输入验证', count: 445, percentage: 12, color: '#3b82f6', trend: 'stable', change: 2 },
|
||||
{ name: '信息泄露', count: 356, percentage: 10, color: '#10b981', trend: 'down', change: 5 }
|
||||
])
|
||||
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 intelRanking = ref([
|
||||
{ country: 'United States', threats: 15420, riskLevel: 'high' },
|
||||
{ country: 'China', threats: 12350, riskLevel: 'high' },
|
||||
{ country: 'Russia', threats: 9870, riskLevel: 'high' },
|
||||
{ country: 'Germany', threats: 6540, riskLevel: 'medium' },
|
||||
{ country: 'Brazil', threats: 4320, riskLevel: 'medium' }
|
||||
])
|
||||
const regionStats = ref([
|
||||
{ name: 'United States', score: 435202 },
|
||||
{ name: 'India', score: 252054 },
|
||||
{ name: 'China (中国)', score: 184085 },
|
||||
{ name: 'Brazil', score: 174811 },
|
||||
{ name: 'Germany', score: 126397 },
|
||||
]);
|
||||
|
||||
const alerts = [
|
||||
{ title: "高危漏洞通告", desc: "Apache Log4j2 远程代码执行漏洞复现" },
|
||||
{ title: "僵尸网络活动", desc: "Mirai 变种正在扫描 IoT 设备端口 23" },
|
||||
{ title: "APT 组织活动", desc: "OceanLotus 组织针对金融行业的钓鱼攻击" },
|
||||
];
|
||||
|
||||
const currentAlert = ref(null);
|
||||
let alertIndex = 0;
|
||||
|
||||
// 地理坐标映射 - 主要城市坐标
|
||||
const geoCoordMap = {
|
||||
'Beijing': [116.4074, 39.9042], // 北京
|
||||
'Shanghai': [121.4737, 31.2304], // 上海
|
||||
'Guangzhou': [113.2644, 23.1291], // 广州
|
||||
'Shenzhen': [114.0579, 22.5431], // 深圳
|
||||
'Washington': [-77.0369, 38.9072], // 华盛顿
|
||||
'NewYork': [-74.0060, 40.7128], // 纽约
|
||||
'Moscow': [37.6173, 55.7558], // 莫斯科
|
||||
'Berlin': [13.4050, 52.5200], // 柏林
|
||||
'Tokyo': [139.6917, 35.6895], // 东京
|
||||
'London': [-0.1278, 51.5074], // 伦敦
|
||||
'Paris': [2.3522, 48.8566], // 巴黎
|
||||
'Sydney': [151.2093, -33.8688], // 悉尼
|
||||
'Mumbai': [72.8777, 19.0760], // 孟买
|
||||
'Dubai': [55.2708, 25.2048], // 迪拜
|
||||
'Toronto': [-79.3832, 43.6532], // 多伦多
|
||||
'SaoPaulo': [-46.6333, -23.5505], // 圣保罗
|
||||
'Cairo': [31.2357, 30.0444], // 开罗
|
||||
'Istanbul': [28.9784, 41.0082], // 伊斯坦布尔
|
||||
'Tehran': [51.3890, 35.6892], // 德黑兰
|
||||
'Riyadh': [46.6753, 24.7136] // 利雅得
|
||||
}
|
||||
'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],
|
||||
};
|
||||
|
||||
import { loadWorldMap } from '@/utils/mapLoader'
|
||||
// 逻辑方法
|
||||
const startAlertRotation = () => {
|
||||
currentAlert.value = alerts[0];
|
||||
timer = setInterval(() => {
|
||||
alertIndex = (alertIndex + 1) % alerts.length;
|
||||
currentAlert.value = null;
|
||||
setTimeout(() => {
|
||||
currentAlert.value = alerts[alertIndex];
|
||||
}, 500);
|
||||
}, 3500);
|
||||
};
|
||||
|
||||
|
||||
|
||||
// 初始化图表
|
||||
const initChart = () => {
|
||||
if (!chartDiv.value) return
|
||||
|
||||
// 地图热力数据
|
||||
const setChart = () => {
|
||||
if (!chartDiv.value) return;
|
||||
echarts.registerMap('world', worldJson);
|
||||
const mapData = [
|
||||
{ name: 'China', value: 100 },
|
||||
{ name: 'United States', value: 95 },
|
||||
{ name: 'Russia', value: 80 },
|
||||
{ name: 'Germany', value: 65 },
|
||||
{ name: 'Brazil', value: 45 },
|
||||
{ name: 'India', value: 55 },
|
||||
{ name: 'Japan', value: 40 },
|
||||
{ name: 'United Kingdom', value: 35 },
|
||||
{ name: 'France', value: 30 },
|
||||
{ name: 'Australia', value: 25 },
|
||||
{ name: 'Canada', value: 35 },
|
||||
{ name: 'Mexico', value: 20 },
|
||||
{ name: 'Argentina', value: 15 },
|
||||
{ name: 'South Africa', value: 18 },
|
||||
{ name: 'Egypt', value: 22 },
|
||||
{ name: 'Turkey', value: 28 },
|
||||
{ name: 'Iran', value: 32 },
|
||||
{ name: 'Saudi Arabia', value: 25 },
|
||||
{ name: 'Kazakhstan', value: 12 },
|
||||
{ name: 'Mongolia', value: 8 }
|
||||
]
|
||||
{ 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 intelNodes = [
|
||||
{ name: '北京情报中心', value: [...geoCoordMap['Beijing'], 100] },
|
||||
{ name: '上海监测点', value: [...geoCoordMap['Shanghai'], 85] },
|
||||
{ name: '广州分析中心', value: [...geoCoordMap['Guangzhou'], 75] },
|
||||
{ name: '深圳预警站', value: [...geoCoordMap['Shenzhen'], 70] },
|
||||
{ name: '华盛顿情报站', value: [...geoCoordMap['Washington'], 90] },
|
||||
{ name: '纽约监控点', value: [...geoCoordMap['NewYork'], 80] },
|
||||
{ name: '莫斯科分析点', value: [...geoCoordMap['Moscow'], 85] },
|
||||
{ name: '柏林预警中心', value: [...geoCoordMap['Berlin'], 65] },
|
||||
{ name: '东京监测站', value: [...geoCoordMap['Tokyo'], 60] },
|
||||
{ name: '伦敦情报点', value: [...geoCoordMap['London'], 55] }
|
||||
]
|
||||
const scatterData = [
|
||||
{ name: '北京中心', value: [...geoCoordMap['Beijing'], 100] },
|
||||
{ name: '上海节点', value: [...geoCoordMap['Shanghai'], 80] },
|
||||
{ name: '广州节点', value: [...geoCoordMap['Guangzhou'], 70] },
|
||||
{ name: '成都节点', value: [...geoCoordMap['Chengdu'], 60] },
|
||||
{ name: '威胁源-US', value: [...geoCoordMap['Washington'], 90] },
|
||||
{ name: '威胁源-RU', value: [...geoCoordMap['Moscow'], 85] },
|
||||
];
|
||||
|
||||
const option = {
|
||||
backgroundColor: 'transparent',
|
||||
|
||||
// 视觉映射组件
|
||||
visualMap: {
|
||||
min: 0,
|
||||
max: 100,
|
||||
left: '20',
|
||||
bottom: '20',
|
||||
left: '30',
|
||||
bottom: '30',
|
||||
text: ['高风险', '低风险'],
|
||||
textStyle: { color: '#64748b', fontSize: 10 },
|
||||
textStyle: { color: '#fff' },
|
||||
calculable: true,
|
||||
inRange: {
|
||||
color: ['#e0e7ff', '#c7d2fe', '#a5b4fc', '#818cf8', '#6366f1']
|
||||
color: ['#1e3c72', '#2a5298', '#24b6d8', '#facc15', '#ef4444']
|
||||
}
|
||||
},
|
||||
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
backgroundColor: 'rgba(255,255,255,0.95)',
|
||||
borderColor: '#e2e8f0',
|
||||
textStyle: { color: '#1f2937' },
|
||||
formatter: function(params) {
|
||||
if(params.seriesType === 'effectScatter') {
|
||||
return `${params.marker} ${params.name}<br/>威胁指数: ${params.value[2]}`
|
||||
backgroundColor: 'rgba(0,0,0,0.8)',
|
||||
borderColor: '#415A77',
|
||||
textStyle: { color: '#fff' },
|
||||
formatter: (params) => {
|
||||
if (params.seriesType === 'effectScatter') {
|
||||
return `${params.marker} ${params.name}<br/>威胁指数: ${params.value[2]}`;
|
||||
}
|
||||
if(!params.value) return params.name + ': 无数据'
|
||||
return `${params.name}<br/>威胁等级: ${params.value}`
|
||||
if (!params.value) return params.name + ': 无数据';
|
||||
return `${params.name}<br/>受影响程度: ${params.value}`;
|
||||
}
|
||||
},
|
||||
|
||||
geo: {
|
||||
map: 'world',
|
||||
roam: true,
|
||||
zoom: 1.2,
|
||||
label: { show: false },
|
||||
itemStyle: {
|
||||
areaColor: '#f8fafc',
|
||||
borderColor: '#e2e8f0',
|
||||
areaColor: '#0f172a',
|
||||
borderColor: '#1e293b',
|
||||
borderWidth: 1
|
||||
},
|
||||
emphasis: {
|
||||
label: { show: false },
|
||||
itemStyle: {
|
||||
areaColor: '#3b82f6',
|
||||
shadowBlur: 10,
|
||||
shadowColor: 'rgba(59, 130, 246, 0.3)'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
series: [
|
||||
// 地域威胁热力图
|
||||
{
|
||||
name: '威胁分布',
|
||||
name: 'Threat Distribution',
|
||||
type: 'map',
|
||||
geoIndex: 0,
|
||||
data: mapData
|
||||
},
|
||||
|
||||
// 情报节点
|
||||
{
|
||||
name: '情报节点',
|
||||
name: 'Key Nodes',
|
||||
type: 'effectScatter',
|
||||
coordinateSystem: 'geo',
|
||||
data: intelNodes,
|
||||
symbolSize: function (val) {
|
||||
return val[2] / 6
|
||||
},
|
||||
data: scatterData,
|
||||
symbolSize: (val) => val[2] / 5,
|
||||
showEffectOn: 'render',
|
||||
rippleEffect: {
|
||||
brushType: 'stroke',
|
||||
scale: 2.5
|
||||
scale: 3
|
||||
},
|
||||
label: {
|
||||
show: true,
|
||||
formatter: '{b}',
|
||||
position: 'right',
|
||||
color: '#1f2937',
|
||||
fontSize: 9,
|
||||
textBorderColor: '#fff',
|
||||
textBorderWidth: 1
|
||||
color: '#fff',
|
||||
fontSize: 10,
|
||||
textBorderColor: '#000',
|
||||
textBorderWidth: 2
|
||||
},
|
||||
itemStyle: {
|
||||
color: '#f59e0b',
|
||||
color: '#facc15',
|
||||
shadowBlur: 10,
|
||||
shadowColor: '#f59e0b'
|
||||
shadowColor: '#facc15'
|
||||
},
|
||||
zlevel: 1
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
myChart.value = echarts.init(chartDiv.value)
|
||||
myChart.value.setOption(option)
|
||||
}
|
||||
myChart = echarts.init(chartDiv.value);
|
||||
myChart.setOption(option);
|
||||
};
|
||||
|
||||
// 组件挂载
|
||||
// 生命周期钩子
|
||||
onMounted(async () => {
|
||||
await loadWorldMap()
|
||||
initChart()
|
||||
await nextTick(); // 确保 DOM 已渲染
|
||||
setChart();
|
||||
startAlertRotation();
|
||||
window.addEventListener('resize', handleResize);
|
||||
});
|
||||
|
||||
window.addEventListener('resize', () => {
|
||||
myChart.value && myChart.value.resize()
|
||||
})
|
||||
})
|
||||
|
||||
// 组件卸载
|
||||
onUnmounted(() => {
|
||||
if (myChart.value) {
|
||||
myChart.value.dispose()
|
||||
onBeforeUnmount(() => {
|
||||
if (timer) clearInterval(timer);
|
||||
window.removeEventListener('resize', handleResize);
|
||||
if (myChart) {
|
||||
myChart.dispose();
|
||||
}
|
||||
window.removeEventListener('resize', () => {
|
||||
myChart.value && myChart.value.resize()
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
const handleResize = () => {
|
||||
myChart && myChart.resize();
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.threat-intel-map {
|
||||
.dashboard-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(135deg, #f8fafc 0%, #e0e7ff 100%);
|
||||
border-radius: 0.5rem;
|
||||
// 背景:深邃的科技蓝黑渐变
|
||||
background: radial-gradient(circle at center, #1e293b 0%, #020617 100%);
|
||||
overflow: hidden;
|
||||
font-family: 'Arial', sans-serif;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.map-chart {
|
||||
.global-map {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* 信息面板通用样式 */
|
||||
.info-panel {
|
||||
/* --- 侧边面板通用样式 --- */
|
||||
.panel {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
width: 260px;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
background: rgba(15, 23, 42, 0.8); /* 半透明深色背景 */
|
||||
backdrop-filter: blur(8px);
|
||||
border: 1px solid rgba(59, 130, 246, 0.2);
|
||||
border-radius: 0.5rem;
|
||||
padding: 16px;
|
||||
border: 1px solid rgba(56, 189, 248, 0.2); /* 科技蓝边框 */
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
z-index: 10;
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
|
||||
|
||||
.panel-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #3b82f6;
|
||||
margin-bottom: 12px;
|
||||
border-bottom: 1px solid rgba(59, 130, 246, 0.2);
|
||||
font-weight: bold;
|
||||
color: #38bdf8;
|
||||
margin-bottom: 15px;
|
||||
border-bottom: 1px solid rgba(56, 189, 248, 0.2);
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
}
|
||||
@@ -297,122 +286,86 @@ onUnmounted(() => {
|
||||
.panel-left { left: 20px; }
|
||||
.panel-right { right: 20px; }
|
||||
|
||||
/* 漏洞类型列表 */
|
||||
.vuln-list {
|
||||
.vuln-item {
|
||||
margin-bottom: 12px;
|
||||
|
||||
.vuln-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 4px;
|
||||
|
||||
.vuln-name {
|
||||
font-size: 12px;
|
||||
color: #374151;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.vuln-count {
|
||||
font-size: 11px;
|
||||
color: #6b7280;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
height: 4px;
|
||||
background: rgba(59, 130, 246, 0.1);
|
||||
border-radius: 2px;
|
||||
overflow: hidden;
|
||||
margin-bottom: 4px;
|
||||
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
border-radius: 2px;
|
||||
transition: width 1s ease;
|
||||
}
|
||||
}
|
||||
|
||||
.vuln-trend {
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
|
||||
&.up { color: #ef4444; }
|
||||
&.down { color: #10b981; }
|
||||
&.stable { color: #6b7280; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 威胁情报排行 */
|
||||
.intel-ranking {
|
||||
.rank-item {
|
||||
/* 左侧:统计列表 */
|
||||
.stat-item {
|
||||
margin-bottom: 12px;
|
||||
.stat-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
padding: 8px;
|
||||
border-radius: 6px;
|
||||
transition: background-color 0.2s;
|
||||
|
||||
&:hover {
|
||||
background: rgba(59, 130, 246, 0.05);
|
||||
}
|
||||
|
||||
.rank-badge {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
color: white;
|
||||
margin-right: 12px;
|
||||
|
||||
&.rank-1 { background: #ef4444; }
|
||||
&.rank-2 { background: #f97316; }
|
||||
&.rank-3 { background: #eab308; }
|
||||
&.rank-4, &.rank-5 { background: #6b7280; }
|
||||
}
|
||||
|
||||
.rank-info {
|
||||
flex: 1;
|
||||
|
||||
.rank-country {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: #1f2937;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.rank-threats {
|
||||
font-size: 10px;
|
||||
color: #6b7280;
|
||||
}
|
||||
}
|
||||
|
||||
.rank-indicator {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
justify-content: space-between;
|
||||
font-size: 12px;
|
||||
color: #cbd5e1;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.progress-bar {
|
||||
height: 6px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 3px;
|
||||
overflow: hidden;
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
border-radius: 3px;
|
||||
transition: width 1s ease;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 右侧:排行列表 */
|
||||
.rank-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
font-size: 12px;
|
||||
|
||||
.rank-num {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
text-align: center;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 4px;
|
||||
margin-right: 10px;
|
||||
color: #94a3b8;
|
||||
font-weight: bold;
|
||||
|
||||
/* 响应式适配 */
|
||||
@media (max-width: 768px) {
|
||||
.info-panel {
|
||||
width: 220px;
|
||||
padding: 12px;
|
||||
top: 15px;
|
||||
&.top-1 { background: #ef4444; color: white; }
|
||||
&.top-2 { background: #f97316; color: white; }
|
||||
&.top-3 { background: #eab308; color: white; }
|
||||
}
|
||||
|
||||
.panel-left { left: 15px; }
|
||||
.panel-right { right: 15px; }
|
||||
.rank-name { flex: 1; color: #e2e8f0; }
|
||||
.rank-score { color: #38bdf8; font-weight: bold; }
|
||||
}
|
||||
|
||||
/* 底部告警卡片 (保持原有风格) */
|
||||
.alert-card {
|
||||
position: absolute;
|
||||
bottom: 20px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%); /* 居中显示 */
|
||||
width: 400px;
|
||||
background: rgba(17, 25, 40, 0.9);
|
||||
border: 1px solid #ef4444;
|
||||
border-radius: 8px;
|
||||
padding: 12px 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
box-shadow: 0 0 20px rgba(239, 68, 68, 0.3);
|
||||
z-index: 10;
|
||||
|
||||
.alert-icon { font-size: 20px; animation: pulse 1.5s infinite; }
|
||||
.alert-content {
|
||||
.alert-title { color: #ef4444; font-weight: bold; font-size: 14px; margin-bottom: 2px; }
|
||||
.alert-desc { color: #fff; font-size: 12px; }
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% { transform: scale(1); opacity: 1; }
|
||||
50% { transform: scale(1.2); opacity: 0.8; }
|
||||
100% { transform: scale(1); opacity: 1; }
|
||||
}
|
||||
|
||||
.fade-slide-enter-active, .fade-slide-leave-active { transition: all 0.5s ease; }
|
||||
.fade-slide-enter-from, .fade-slide-leave-to { opacity: 0; transform: translate(-50%, 20px); }
|
||||
</style>
|
||||
1
src/views/Jyh/security/components/world.json
Normal file
1
src/views/Jyh/security/components/world.json
Normal file
File diff suppressed because one or more lines are too long
@@ -10,7 +10,6 @@
|
||||
<div class="index-module__NV_5cW__titleSection">
|
||||
<div class="index-module__NV_5cW__titleContainer">
|
||||
<h1 class="index-module__NV_5cW__title">全球开源风险态势感知监控中心</h1>
|
||||
<div class="index-module__NV_5cW__titleBadge">WOS</div>
|
||||
</div>
|
||||
<!-- <div class="index-module__NV_5cW__subtitle">-->
|
||||
<!-- <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none"-->
|
||||
@@ -139,7 +138,7 @@
|
||||
</div>
|
||||
|
||||
<!-- 编程语言使用趋势堆叠面积图 -->
|
||||
<div class="index-module__NV_5cW__chartCard chartCard__wide">
|
||||
<div class="index-module__NV_5cW__chartCard chartCard__wide mb-[1.5rem]">
|
||||
<h3 class="chartCard__title">漏洞语言分布</h3>
|
||||
<div class="chartCard__container">
|
||||
<div class="num-empty" v-if="isEmpty.languageTrend"></div>
|
||||
@@ -148,7 +147,7 @@
|
||||
</div>
|
||||
|
||||
<!-- 高危漏洞占比图表 -->
|
||||
<div class="index-module__NV_5cW__chartCard">
|
||||
<div class="index-module__NV_5cW__chartCard mb-[1.5rem]">
|
||||
<h3 class="chartCard__title">漏洞等级分布</h3>
|
||||
<div class="chartCard__container">
|
||||
<div class="num-empty" v-if="isEmpty.vulnerabilitySeverity"></div>
|
||||
@@ -157,90 +156,13 @@
|
||||
</div>
|
||||
|
||||
<!-- 气泡图 -->
|
||||
<div class="index-module__NV_5cW__chartCard">
|
||||
<div class="index-module__NV_5cW__chartCard mb-[1.5rem]">
|
||||
<h3 class="chartCard__title">编程语言安全态势</h3>
|
||||
<div class="chartCard__container">
|
||||
<div class="num-empty" v-if="isEmpty.scatterData"></div>
|
||||
<div v-else id="scatterChart" class="chart"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 加入武汉开源社区行动区 -->
|
||||
<div class="index-module__NV_5cW__actionSection">
|
||||
<div class="index-module__NV_5cW__actionBackground"></div>
|
||||
<div class="index-module__NV_5cW__actionContent">
|
||||
<div class="index-module__NV_5cW__actionIcon">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
||||
class="lucide lucide-sparkles" aria-hidden="true">
|
||||
<path
|
||||
d="M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z"></path>
|
||||
<path d="M20 3v4"></path>
|
||||
<path d="M22 5h-4"></path>
|
||||
<path d="M4 17v2"></path>
|
||||
<path d="M5 18H3"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="index-module__NV_5cW__actionTitle">加入武汉开源社区</h3>
|
||||
<p class="index-module__NV_5cW__actionDescription">
|
||||
与武汉本地开发者、企业、高校携手共建开源生态,共享开源成果,参与社区活动与项目协作
|
||||
</p>
|
||||
<div class="index-module__NV_5cW__actionFeatures">
|
||||
<div class="index-module__NV_5cW__feature">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
||||
class="lucide lucide-users" aria-hidden="true">
|
||||
<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"></path>
|
||||
<path d="M16 3.128a4 4 0 0 1 0 7.744"></path>
|
||||
<path d="M22 21v-2a4 4 0 0 0-3-3.87"></path>
|
||||
<circle cx="9" cy="7" r="4"></circle>
|
||||
</svg>
|
||||
<span>本地技术交流</span>
|
||||
</div>
|
||||
<div class="index-module__NV_5cW__feature">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
||||
class="lucide lucide-building2" aria-hidden="true">
|
||||
<path d="M6 22V4a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v18Z"></path>
|
||||
<path d="M6 12H4a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2h2"></path>
|
||||
<path d="M18 9h2a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-2"></path>
|
||||
<path d="M10 6h4"></path>
|
||||
<path d="M10 10h4"></path>
|
||||
<path d="M10 14h4"></path>
|
||||
<path d="M10 18h4"></path>
|
||||
</svg>
|
||||
<span>企业项目协作</span>
|
||||
</div>
|
||||
<div class="index-module__NV_5cW__feature">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
||||
class="lucide lucide-graduation-cap" aria-hidden="true">
|
||||
<path d="M22 10v6M2 10l10-5 10 5-10 5z"></path>
|
||||
<path d="M6 12v5c3 3 9 3 12 0v-5"></path>
|
||||
</svg>
|
||||
<span>高校产学研对接</span>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button"
|
||||
class="ant-btn css-aa1tt5 ant-btn-primary ant-btn-color-primary ant-btn-variant-solid ant-btn-lg index-module__NV_5cW__createButton">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
||||
class="lucide lucide-user-plus" aria-hidden="true">
|
||||
<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"></path>
|
||||
<circle cx="9" cy="7" r="4"></circle>
|
||||
<path d="M22 21v-2a4 4 0 0 0-3-3.87"></path>
|
||||
<path d="M16 3.12a4 4 0 0 1 0 7.76"></path>
|
||||
</svg>
|
||||
<span>加入武汉开源社区</span>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
||||
class="lucide lucide-arrow-right" aria-hidden="true">
|
||||
<path d="M5 12h14"></path>
|
||||
<path d="m12 5 7 7-7 7"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1601,7 +1523,7 @@ onMounted(() => {
|
||||
|
||||
/* 图表区域样式(适配示例的num-empty) */
|
||||
.index-module__NV_5cW__chartsSection {
|
||||
margin-bottom: 3rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.index-module__NV_5cW__chartsGrid {
|
||||
|
||||
Reference in New Issue
Block a user