删除第一张风险监控地图(SecurityRiskMap)及相关代码
This commit is contained in:
468
src/views/Jyh/security/components/ChinaSecurityMap.vue
Normal file
468
src/views/Jyh/security/components/ChinaSecurityMap.vue
Normal file
@@ -0,0 +1,468 @@
|
||||
<template>
|
||||
<div class="china-security-map">
|
||||
<div ref="chartDiv" class="map-chart"></div>
|
||||
|
||||
<!-- 左侧省份威胁排行 -->
|
||||
<div class="info-panel panel-left">
|
||||
<div class="panel-title">🏛️ 省份威胁排行</div>
|
||||
<div class="province-ranking">
|
||||
<div class="rank-item" v-for="(item, index) in provinceRanking" :key="index">
|
||||
<div class="rank-badge" :class="'rank-' + (index + 1)">{{ index + 1 }}</div>
|
||||
<div class="rank-info">
|
||||
<div class="rank-province">{{ item.province }}</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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧安全态势面板 -->
|
||||
<div class="info-panel panel-right">
|
||||
<div class="panel-title">📈 安全态势分析</div>
|
||||
<div class="security-stats">
|
||||
<div class="stat-item" v-for="(stat, index) in securityStats" :key="index">
|
||||
<div class="stat-icon" :style="{ color: stat.color }">{{ stat.icon }}</div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-value" :style="{ color: stat.color }">{{ stat.value }}</div>
|
||||
<div class="stat-label">{{ stat.label }}</div>
|
||||
<div class="stat-trend" :class="stat.trend">
|
||||
{{ stat.trend === 'up' ? '↗' : stat.trend === 'down' ? '↘' : '→' }} {{ stat.change }}%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
import * as echarts from 'echarts'
|
||||
|
||||
// 响应式数据
|
||||
const chartDiv = ref(null)
|
||||
const myChart = ref(null)
|
||||
|
||||
// 省份威胁排行
|
||||
const provinceRanking = ref([
|
||||
{ province: '北京', threats: 8420, riskLevel: 'high' },
|
||||
{ province: '上海', threats: 7350, riskLevel: 'high' },
|
||||
{ province: '广东', threats: 6870, riskLevel: 'high' },
|
||||
{ province: '浙江', threats: 5540, riskLevel: 'medium' },
|
||||
{ province: '江苏', threats: 4320, riskLevel: 'medium' },
|
||||
{ province: '山东', threats: 3890, riskLevel: 'medium' },
|
||||
{ province: '四川', threats: 2650, riskLevel: 'low' }
|
||||
])
|
||||
|
||||
// 安全态势统计
|
||||
const securityStats = ref([
|
||||
{ icon: '🔥', label: '活跃攻击', value: '2,847', color: '#ef4444', trend: 'up', change: 15 },
|
||||
{ icon: '🛡️', label: '防护成功', value: '15,692', color: '#10b981', trend: 'up', change: 8 },
|
||||
{ icon: '⚠️', label: '高危漏洞', value: '156', color: '#f59e0b', trend: 'down', change: 12 },
|
||||
{ icon: '🎯', label: '精准拦截', value: '98.7%', color: '#8b5cf6', trend: 'up', change: 2 }
|
||||
])
|
||||
|
||||
// 主要城市坐标映射
|
||||
const geoCoordMap = {
|
||||
'北京': [116.4074, 39.9042],
|
||||
'上海': [121.4737, 31.2304],
|
||||
'广州': [113.2644, 23.1291],
|
||||
'深圳': [114.0579, 22.5431],
|
||||
'杭州': [120.1551, 30.2741],
|
||||
'南京': [118.7969, 32.0603],
|
||||
'武汉': [114.3054, 30.5931],
|
||||
'成都': [104.0665, 30.5723],
|
||||
'西安': [108.9398, 34.3416],
|
||||
'重庆': [106.5516, 29.5630],
|
||||
'天津': [117.2008, 39.0842],
|
||||
'沈阳': [123.4315, 41.8057],
|
||||
'长沙': [112.9388, 28.2282],
|
||||
'郑州': [113.6254, 34.7466],
|
||||
'济南': [117.1205, 36.6519],
|
||||
'哈尔滨': [126.5358, 45.8023],
|
||||
'长春': [125.3245, 43.8868],
|
||||
'石家庄': [114.5149, 38.0428],
|
||||
'太原': [112.5489, 37.8706],
|
||||
'呼和浩特': [111.7519, 40.8414]
|
||||
}
|
||||
|
||||
import { loadChinaMap } from '@/utils/mapLoader'
|
||||
|
||||
// 初始化图表
|
||||
const initChart = () => {
|
||||
if (!chartDiv.value) return
|
||||
|
||||
// 省份威胁热力数据
|
||||
const provinceData = [
|
||||
{ name: '北京', value: 100 },
|
||||
{ name: '上海', value: 95 },
|
||||
{ name: '广东', value: 90 },
|
||||
{ name: '浙江', value: 75 },
|
||||
{ name: '江苏', value: 70 },
|
||||
{ name: '山东', value: 65 },
|
||||
{ name: '四川', value: 60 },
|
||||
{ name: '湖北', value: 55 },
|
||||
{ name: '河南', value: 50 },
|
||||
{ name: '湖南', value: 45 },
|
||||
{ name: '安徽', value: 40 },
|
||||
{ name: '河北', value: 38 },
|
||||
{ name: '福建', value: 35 },
|
||||
{ name: '江西', value: 32 },
|
||||
{ name: '重庆', value: 30 },
|
||||
{ name: '陕西', value: 28 },
|
||||
{ name: '辽宁', value: 25 },
|
||||
{ name: '天津', value: 22 },
|
||||
{ name: '山西', value: 20 },
|
||||
{ name: '吉林', value: 18 },
|
||||
{ name: '黑龙江', value: 15 },
|
||||
{ name: '内蒙古', value: 12 },
|
||||
{ name: '广西', value: 25 },
|
||||
{ name: '海南', value: 10 },
|
||||
{ name: '贵州', value: 15 },
|
||||
{ name: '云南', value: 20 },
|
||||
{ name: '西藏', value: 5 },
|
||||
{ name: '甘肃', value: 8 },
|
||||
{ name: '青海', value: 3 },
|
||||
{ name: '宁夏', value: 6 },
|
||||
{ name: '新疆', value: 10 },
|
||||
{ name: '台湾', value: 30 },
|
||||
{ name: '香港', value: 25 },
|
||||
{ name: '澳门', value: 15 }
|
||||
]
|
||||
|
||||
// 安全监控节点
|
||||
const securityNodes = [
|
||||
{ name: '北京安全中心', value: [...geoCoordMap['北京'], 100] },
|
||||
{ name: '上海监控点', value: [...geoCoordMap['上海'], 85] },
|
||||
{ name: '广州分析中心', value: [...geoCoordMap['广州'], 75] },
|
||||
{ name: '深圳预警站', value: [...geoCoordMap['深圳'], 70] },
|
||||
{ name: '杭州情报点', value: [...geoCoordMap['杭州'], 65] },
|
||||
{ name: '南京监测站', value: [...geoCoordMap['南京'], 60] },
|
||||
{ name: '武汉防护点', value: [...geoCoordMap['武汉'], 55] },
|
||||
{ name: '成都安全站', value: [...geoCoordMap['成都'], 50] }
|
||||
]
|
||||
|
||||
// 攻击路径数据
|
||||
const attackPaths = [
|
||||
{ coords: [geoCoordMap['北京'], geoCoordMap['上海']] },
|
||||
{ coords: [geoCoordMap['广州'], geoCoordMap['深圳']] },
|
||||
{ coords: [geoCoordMap['杭州'], geoCoordMap['南京']] },
|
||||
{ coords: [geoCoordMap['武汉'], geoCoordMap['成都']] }
|
||||
]
|
||||
|
||||
const option = {
|
||||
backgroundColor: 'transparent',
|
||||
|
||||
// 视觉映射组件
|
||||
visualMap: {
|
||||
min: 0,
|
||||
max: 100,
|
||||
left: '20',
|
||||
bottom: '20',
|
||||
text: ['高风险', '低风险'],
|
||||
textStyle: { color: '#64748b', fontSize: 10 },
|
||||
calculable: true,
|
||||
inRange: {
|
||||
color: ['#e0f2fe', '#bae6fd', '#7dd3fc', '#38bdf8', '#0ea5e9']
|
||||
}
|
||||
},
|
||||
|
||||
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]}`
|
||||
}
|
||||
if(!params.value) return params.name + ': 无数据'
|
||||
return `${params.name}<br/>威胁等级: ${params.value}`
|
||||
}
|
||||
},
|
||||
|
||||
geo: {
|
||||
map: 'china',
|
||||
roam: true,
|
||||
zoom: 1.2,
|
||||
label: {
|
||||
show: true,
|
||||
fontSize: 10,
|
||||
color: '#64748b'
|
||||
},
|
||||
itemStyle: {
|
||||
areaColor: '#f8fafc',
|
||||
borderColor: '#cbd5e1',
|
||||
borderWidth: 1
|
||||
},
|
||||
emphasis: {
|
||||
label: {
|
||||
show: true,
|
||||
color: '#1f2937'
|
||||
},
|
||||
itemStyle: {
|
||||
areaColor: '#0ea5e9',
|
||||
shadowBlur: 10,
|
||||
shadowColor: 'rgba(14, 165, 233, 0.3)'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
series: [
|
||||
// 省份威胁热力图
|
||||
{
|
||||
name: '威胁分布',
|
||||
type: 'map',
|
||||
geoIndex: 0,
|
||||
data: provinceData
|
||||
},
|
||||
|
||||
// 安全监控节点
|
||||
{
|
||||
name: '安全节点',
|
||||
type: 'effectScatter',
|
||||
coordinateSystem: 'geo',
|
||||
data: securityNodes,
|
||||
symbolSize: function (val) {
|
||||
return val[2] / 8
|
||||
},
|
||||
showEffectOn: 'render',
|
||||
rippleEffect: {
|
||||
brushType: 'stroke',
|
||||
scale: 2.5
|
||||
},
|
||||
label: {
|
||||
show: true,
|
||||
formatter: '{b}',
|
||||
position: 'right',
|
||||
color: '#1f2937',
|
||||
fontSize: 9,
|
||||
textBorderColor: '#fff',
|
||||
textBorderWidth: 1
|
||||
},
|
||||
itemStyle: {
|
||||
color: '#10b981',
|
||||
shadowBlur: 10,
|
||||
shadowColor: '#10b981'
|
||||
},
|
||||
zlevel: 2
|
||||
},
|
||||
|
||||
// 攻击路径 (红色飞线)
|
||||
{
|
||||
type: 'lines',
|
||||
zlevel: 1,
|
||||
effect: {
|
||||
show: true,
|
||||
period: 4,
|
||||
trailLength: 0.7,
|
||||
color: '#ef4444',
|
||||
symbolSize: 3
|
||||
},
|
||||
lineStyle: {
|
||||
color: '#ef4444',
|
||||
width: 0,
|
||||
curveness: 0.2
|
||||
},
|
||||
data: attackPaths
|
||||
},
|
||||
|
||||
// 静态连线背景
|
||||
{
|
||||
type: 'lines',
|
||||
zlevel: 0,
|
||||
symbol: ['none', 'arrow'],
|
||||
symbolSize: 6,
|
||||
effect: { show: false },
|
||||
lineStyle: {
|
||||
color: '#ef4444',
|
||||
width: 1,
|
||||
opacity: 0.3,
|
||||
curveness: 0.2
|
||||
},
|
||||
data: attackPaths
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
myChart.value = echarts.init(chartDiv.value)
|
||||
myChart.value.setOption(option)
|
||||
}
|
||||
|
||||
// 组件挂载
|
||||
onMounted(async () => {
|
||||
await loadChinaMap()
|
||||
initChart()
|
||||
|
||||
window.addEventListener('resize', () => {
|
||||
myChart.value && myChart.value.resize()
|
||||
})
|
||||
})
|
||||
|
||||
// 组件卸载
|
||||
onUnmounted(() => {
|
||||
if (myChart.value) {
|
||||
myChart.value.dispose()
|
||||
}
|
||||
window.removeEventListener('resize', () => {
|
||||
myChart.value && myChart.value.resize()
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.china-security-map {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%);
|
||||
border-radius: 0.5rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.map-chart {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* 信息面板通用样式 */
|
||||
.info-panel {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
width: 260px;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
backdrop-filter: blur(8px);
|
||||
border: 1px solid rgba(14, 165, 233, 0.2);
|
||||
border-radius: 0.5rem;
|
||||
padding: 16px;
|
||||
z-index: 10;
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
|
||||
|
||||
.panel-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #0ea5e9;
|
||||
margin-bottom: 12px;
|
||||
border-bottom: 1px solid rgba(14, 165, 233, 0.2);
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.panel-left { left: 20px; }
|
||||
.panel-right { right: 20px; }
|
||||
|
||||
/* 省份威胁排行 */
|
||||
.province-ranking {
|
||||
.rank-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
padding: 8px;
|
||||
border-radius: 6px;
|
||||
transition: background-color 0.2s;
|
||||
|
||||
&:hover {
|
||||
background: rgba(14, 165, 233, 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, &.rank-6, &.rank-7 { background: #6b7280; }
|
||||
}
|
||||
|
||||
.rank-info {
|
||||
flex: 1;
|
||||
|
||||
.rank-province {
|
||||
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%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 安全态势统计 */
|
||||
.security-stats {
|
||||
.stat-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
padding: 8px;
|
||||
border-radius: 6px;
|
||||
transition: background-color 0.2s;
|
||||
|
||||
&:hover {
|
||||
background: rgba(14, 165, 233, 0.05);
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
font-size: 20px;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.stat-content {
|
||||
flex: 1;
|
||||
|
||||
.stat-value {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 11px;
|
||||
color: #6b7280;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.stat-trend {
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
|
||||
&.up { color: #ef4444; }
|
||||
&.down { color: #10b981; }
|
||||
&.stable { color: #6b7280; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 响应式适配 */
|
||||
@media (max-width: 768px) {
|
||||
.info-panel {
|
||||
width: 220px;
|
||||
padding: 12px;
|
||||
top: 15px;
|
||||
}
|
||||
|
||||
.panel-left { left: 15px; }
|
||||
.panel-right { right: 15px; }
|
||||
}
|
||||
</style>
|
||||
418
src/views/Jyh/security/components/ThreatIntelMap.vue
Normal file
418
src/views/Jyh/security/components/ThreatIntelMap.vue
Normal file
@@ -0,0 +1,418 @@
|
||||
<template>
|
||||
<div class="threat-intel-map">
|
||||
<div ref="chartDiv" class="map-chart"></div>
|
||||
|
||||
<!-- 左侧漏洞类型面板 -->
|
||||
<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>
|
||||
</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>
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
import * as echarts from 'echarts'
|
||||
|
||||
// 响应式数据
|
||||
const chartDiv = ref(null)
|
||||
const myChart = ref(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 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 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] // 利雅得
|
||||
}
|
||||
|
||||
import { loadWorldMap } from '@/utils/mapLoader'
|
||||
|
||||
|
||||
|
||||
// 初始化图表
|
||||
const initChart = () => {
|
||||
if (!chartDiv.value) return
|
||||
|
||||
// 地图热力数据
|
||||
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 }
|
||||
]
|
||||
|
||||
// 威胁情报节点
|
||||
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 option = {
|
||||
backgroundColor: 'transparent',
|
||||
|
||||
// 视觉映射组件
|
||||
visualMap: {
|
||||
min: 0,
|
||||
max: 100,
|
||||
left: '20',
|
||||
bottom: '20',
|
||||
text: ['高风险', '低风险'],
|
||||
textStyle: { color: '#64748b', fontSize: 10 },
|
||||
calculable: true,
|
||||
inRange: {
|
||||
color: ['#e0e7ff', '#c7d2fe', '#a5b4fc', '#818cf8', '#6366f1']
|
||||
}
|
||||
},
|
||||
|
||||
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]}`
|
||||
}
|
||||
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',
|
||||
borderWidth: 1
|
||||
},
|
||||
emphasis: {
|
||||
label: { show: false },
|
||||
itemStyle: {
|
||||
areaColor: '#3b82f6',
|
||||
shadowBlur: 10,
|
||||
shadowColor: 'rgba(59, 130, 246, 0.3)'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
series: [
|
||||
// 地域威胁热力图
|
||||
{
|
||||
name: '威胁分布',
|
||||
type: 'map',
|
||||
geoIndex: 0,
|
||||
data: mapData
|
||||
},
|
||||
|
||||
// 情报节点
|
||||
{
|
||||
name: '情报节点',
|
||||
type: 'effectScatter',
|
||||
coordinateSystem: 'geo',
|
||||
data: intelNodes,
|
||||
symbolSize: function (val) {
|
||||
return val[2] / 6
|
||||
},
|
||||
showEffectOn: 'render',
|
||||
rippleEffect: {
|
||||
brushType: 'stroke',
|
||||
scale: 2.5
|
||||
},
|
||||
label: {
|
||||
show: true,
|
||||
formatter: '{b}',
|
||||
position: 'right',
|
||||
color: '#1f2937',
|
||||
fontSize: 9,
|
||||
textBorderColor: '#fff',
|
||||
textBorderWidth: 1
|
||||
},
|
||||
itemStyle: {
|
||||
color: '#f59e0b',
|
||||
shadowBlur: 10,
|
||||
shadowColor: '#f59e0b'
|
||||
},
|
||||
zlevel: 1
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
myChart.value = echarts.init(chartDiv.value)
|
||||
myChart.value.setOption(option)
|
||||
}
|
||||
|
||||
// 组件挂载
|
||||
onMounted(async () => {
|
||||
await loadWorldMap()
|
||||
initChart()
|
||||
|
||||
window.addEventListener('resize', () => {
|
||||
myChart.value && myChart.value.resize()
|
||||
})
|
||||
})
|
||||
|
||||
// 组件卸载
|
||||
onUnmounted(() => {
|
||||
if (myChart.value) {
|
||||
myChart.value.dispose()
|
||||
}
|
||||
window.removeEventListener('resize', () => {
|
||||
myChart.value && myChart.value.resize()
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.threat-intel-map {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(135deg, #f8fafc 0%, #e0e7ff 100%);
|
||||
border-radius: 0.5rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.map-chart {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* 信息面板通用样式 */
|
||||
.info-panel {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
width: 260px;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
backdrop-filter: blur(8px);
|
||||
border: 1px solid rgba(59, 130, 246, 0.2);
|
||||
border-radius: 0.5rem;
|
||||
padding: 16px;
|
||||
z-index: 10;
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
|
||||
|
||||
.panel-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #3b82f6;
|
||||
margin-bottom: 12px;
|
||||
border-bottom: 1px solid rgba(59, 130, 246, 0.2);
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.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 {
|
||||
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%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* 响应式适配 */
|
||||
@media (max-width: 768px) {
|
||||
.info-panel {
|
||||
width: 220px;
|
||||
padding: 12px;
|
||||
top: 15px;
|
||||
}
|
||||
|
||||
.panel-left { left: 15px; }
|
||||
.panel-right { right: 15px; }
|
||||
}
|
||||
</style>
|
||||
0
src/views/Jyh/security/components/worldMapData.js
Normal file
0
src/views/Jyh/security/components/worldMapData.js
Normal file
@@ -66,7 +66,15 @@
|
||||
|
||||
<!-- 图表网格布局:新增多个模块 -->
|
||||
<div class="index-module__NV_5cW__chartsGrid">
|
||||
<!-- 1. 武汉优秀开源项目(列表展示)-->
|
||||
<!-- 1. 全球威胁情报分析地图 -->
|
||||
<div class="index-module__NV_5cW__chartCard chartCard__wide">
|
||||
<h3 class="chartCard__title">🛡️ 全球威胁情报分析与监测</h3>
|
||||
<div class="chartCard__container map-container">
|
||||
<ThreatIntelMap />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 2. 武汉优秀开源项目(列表展示)-->
|
||||
<div class="index-module__NV_5cW__chartCard list-card">
|
||||
<h3 class="chartCard__title">武汉优秀开源项目</h3>
|
||||
<!-- 新增:ref + 鼠标事件 -->
|
||||
@@ -87,7 +95,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 2. 高校开源参与分布(饼图)-->
|
||||
<!-- 3. 高校开源参与分布(饼图)-->
|
||||
<div class="index-module__NV_5cW__chartCard">
|
||||
<h3 class="chartCard__title">高校开源参与分布</h3>
|
||||
<div class="chartCard__container">
|
||||
@@ -96,7 +104,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 3. 开源社区区域分布(柱状图)-->
|
||||
<!-- 4. 开源社区区域分布(柱状图)-->
|
||||
<div class="index-module__NV_5cW__chartCard">
|
||||
<h3 class="chartCard__title">开源社区区域分布</h3>
|
||||
<div class="chartCard__container">
|
||||
@@ -105,7 +113,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 4. OSS Compass开源项目排行(横向柱状图)-->
|
||||
<!-- 5. OSS Compass开源项目排行(横向柱状图)-->
|
||||
<div class="index-module__NV_5cW__chartCard">
|
||||
<h3 class="chartCard__title">OSS Compass 开源项目排行</h3>
|
||||
<div class="chartCard__container">
|
||||
@@ -114,7 +122,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 5. 武汉开源开发者排行榜(横向柱状图)-->
|
||||
<!-- 6. 武汉开源开发者排行榜(横向柱状图)-->
|
||||
<div class="index-module__NV_5cW__chartCard">
|
||||
<h3 class="chartCard__title">武汉开源开发者排行榜</h3>
|
||||
<div class="chartCard__container">
|
||||
@@ -123,7 +131,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 6. 2025武汉市优秀开源软件项目(列表)-->
|
||||
<!-- 7. 2025武汉市优秀开源软件项目(列表)-->
|
||||
<div class="index-module__NV_5cW__chartCard list-card">
|
||||
<h3 class="chartCard__title">2025武汉市优秀开源软件项目</h3>
|
||||
<!-- 新增:ref + 鼠标事件 -->
|
||||
@@ -144,7 +152,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 7. 武汉市语言与技术竞争趋势(折线图,近五年)-->
|
||||
<!-- 8. 武汉市语言与技术竞争趋势(折线图,近五年)-->
|
||||
<div class="index-module__NV_5cW__chartCard chartCard__wide">
|
||||
<h3 class="chartCard__title">武汉市语言与技术竞争趋势(2021-2025)</h3>
|
||||
<div class="chartCard__container">
|
||||
@@ -239,6 +247,7 @@
|
||||
import { onMounted, ref, nextTick, watch, onUnmounted } from 'vue';
|
||||
import * as echarts from 'echarts';
|
||||
import { usePageResize } from '@/utils/hooks/usePageResize';
|
||||
import ThreatIntelMap from './components/ThreatIntelMap.vue';
|
||||
|
||||
// 页面尺寸响应式
|
||||
const { widthType } = usePageResize();
|
||||
@@ -952,6 +961,26 @@ onMounted(() => {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 地图容器特殊样式 */
|
||||
.chartCard__container.map-container {
|
||||
height: 500px; /* 地图需要更大的高度 */
|
||||
border-radius: 0.5rem;
|
||||
overflow: hidden;
|
||||
background: transparent; /* 让地图组件自己控制背景 */
|
||||
}
|
||||
|
||||
/* 地图卡片标题样式优化 */
|
||||
.index-module__NV_5cW__chartCard:has(.map-container) .chartCard__title {
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
backdrop-filter: blur(8px);
|
||||
border-radius: 20px;
|
||||
padding: 8px 16px;
|
||||
margin: 0 auto 16px;
|
||||
width: fit-content;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
border: 1px solid rgba(59, 130, 246, 0.2);
|
||||
}
|
||||
|
||||
/* 列表容器样式:隐藏原生滚动条(可选,视觉更美观) */
|
||||
.chartCard__container.list-container {
|
||||
height: auto;
|
||||
|
||||
Reference in New Issue
Block a user