Files
Situation-Awareness-Platfor…/src/views/Jyh/ecosystem/components/talentMapChart.vue

477 lines
16 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="map-container">
<div id="talentMapChart" class="chart" v-if="!loading"></div>
<div class="num-empty" v-else>加载中...</div>
<!-- 右侧排行榜 -->
<div class="map-rank-panel">
<div class="panel-title">🏆 开源人才数量排行 TOP 5</div>
<div class="rank-list">
<div class="rank-item" v-for="(item, index) in topRegions" :key="index">
<span :class="['rank-num', 'rank-' + (index + 1)]">{{ index + 1 }}</span>
<span class="rank-name">{{ item.name }}</span>
<span class="rank-score">{{ item.score }} 万人</span>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import {onMounted, ref, onBeforeUnmount, computed} from 'vue';
// 初始化中国开源人才密度地图
import * as echarts from "echarts";
import geoJson from '../json/china.json';
import { talentMapDataPage2 } from '@/api/jyh/situation';
const talentMapChart = ref()
// 定义数据结构
interface TalentMapItem {
name: string;
density: number;
total: number;
}
// 中国开源人才密度地图数据
const talentMapData = ref<any[]>([]);
const loading = ref(true);
// 从API获取数据
const fetchDataFromApi = async () => {
try {
loading.value = true;
const response = await talentMapDataPage2();
const businessData = response.data; // Axios响应格式业务数据在response.data中
if (businessData?.code === 200 && businessData.data) {
// 转换API返回的数据格式为图表所需格式
talentMapData.value = businessData.data.map((item: TalentMapItem) => {
// 查找省份坐标,如果找不到则使用默认坐标
let coordinates = [104.5, 37.5]; // 默认为中国中心坐标
// 匹配省份坐标
const provinceCoords: Record<string, [number, number]> = {
'北京市': [116.4074, 39.9042],
'上海市': [121.4737, 31.2304],
'浙江省': [120.1551, 30.2794],
'广东省': [113.2644, 23.1291],
'台湾省': [121.5090, 25.0443],
'四川省': [104.0657, 30.6595],
'江苏省': [118.7780, 32.0472],
'湖北省': [114.3053, 30.5928],
'福建省': [119.3062, 26.0747],
'陕西省': [108.9480, 34.2632],
'重庆市': [106.5049, 29.5332],
'山东省': [120.3331, 36.0670],
'湖南省': [112.9822, 28.1944],
'安徽省': [117.2830, 31.8611],
'辽宁省': [123.4321, 41.8007],
'云南省': [102.7123, 25.0406],
'天津市': [117.1901, 39.1258],
'河南省': [113.6654, 34.7579],
'吉林省': [125.3245, 43.8171],
'江西省': [115.8581, 28.6820],
'广西壮族自治区': [108.3200, 22.8241],
'黑龙江省': [126.5349, 45.8038],
'澳门特别行政区': [113.5490, 22.1987],
'河北省': [114.5024, 38.0454],
'山西省': [112.5489, 37.8570],
'贵州省': [106.7135, 26.5783],
'内蒙古自治区': [111.6708, 40.8183],
'香港特别行政区': [114.1095, 22.3964],
'新疆维吾尔自治区': [87.6168, 43.7928],
'甘肃省': [103.8235, 36.0580],
'海南省': [110.3312, 20.0319],
'宁夏回族自治区': [106.2782, 38.4664],
'西藏自治区': [91.1175, 29.6469],
'青海省': [101.7782, 36.6172]
};
const coord = provinceCoords[item.name] || coordinates;
// 返回格式:[经度, 纬度, 密度, 总量]
return {
name: item.name,
value: [coord[0], coord[1], item.density, item.total]
};
});
} else {
console.error('获取人才地图数据失败:', businessData?.msg || '未知错误');
// 设置默认数据
talentMapData.value = [
{ name: '北京市', value: [116.4074, 39.9042, 141.15, 231.47] },
{ name: '上海市', value: [121.4737, 31.2304, 269.83, 169.99] },
{ name: '浙江省', value: [120.1551, 30.2794, 8.94, 90.98] },
{ name: '广东省', value: [113.2644, 23.1291, 7.5, 149.76] },
{ name: '台湾省', value: [121.5090, 25.0443, 32.75, 117.88] },
{ name: '四川省', value: [104.0657, 30.6595, 1.01, 48.7] },
{ name: '江苏省', value: [118.7780, 32.0472, 5.25, 56.28] },
{ name: '湖北省', value: [114.3053, 30.5928, 1.84, 34.09] },
{ name: '福建省', value: [119.3062, 26.0747, 1.39, 16.79] },
{ name: '陕西省', value: [108.9480, 34.2632, 1.04, 21.38] },
{ name: '重庆市', value: [106.5049, 29.5332, 1.61, 13.24] },
{ name: '山东省', value: [120.3331, 36.0670, 1.02, 16.02] },
{ name: '湖南省', value: [112.9822, 28.1944, 0.68, 14.25] },
{ name: '安徽省', value: [117.2830, 31.8611, 0.93, 12.92] },
{ name: '辽宁省', value: [123.4321, 41.8007, 0.77, 11.37] },
{ name: '云南省', value: [102.7123, 25.0406, 0.1, 3.68] },
{ name: '天津市', value: [117.1901, 39.1258, 7.79, 9.34] },
{ name: '河南省', value: [113.6654, 34.7579, 0.55, 9.07] },
{ name: '吉林省', value: [125.3245, 43.8171, 0.15, 2.7] },
{ name: '江西省', value: [115.8581, 28.6820, 0.25, 4.17] },
{ name: '广西壮族自治区', value: [108.3200, 22.8241, 0.14, 3.15] },
{ name: '黑龙江省', value: [126.5349, 45.8038, 0.11, 5.13] },
{ name: '澳门特别行政区', value: [113.5490, 22.1987, 737.5, 2.36] },
{ name: '河北省', value: [114.5024, 38.0454, 0.26, 4.74] },
{ name: '山西省', value: [112.5489, 37.8570, 0.25, 3.78] },
{ name: '贵州省', value: [106.7135, 26.5783, 0.11, 1.93] },
{ name: '内蒙古自治区', value: [111.6708, 40.8183, 0.01, 0.96] },
{ name: '香港特别行政区', value: [114.1095, 22.3964, 1797.7, 197.75] },
{ name: '新疆维吾尔自治区', value: [87.6168, 43.7928, 0.01, 0.77] },
{ name: '甘肃省', value: [103.8235, 36.0580, 0.04, 1.46] },
{ name: '海南省', value: [110.3312, 20.0319, 0.36, 1.25] },
{ name: '宁夏回族自治区', value: [106.2782, 38.4664, 0.05, 0.27] },
{ name: '西藏自治区', value: [91.1175, 29.6469, 0.01, 0.14] },
{ name: '青海省', value: [101.7782, 36.6172, 0.01, 0.18] }
];
}
} catch (error) {
console.error('获取人才地图数据异常:', error);
// 设置默认数据
talentMapData.value = [
{ name: '北京市', value: [116.4074, 39.9042, 141.15, 231.47] },
{ name: '上海市', value: [121.4737, 31.2304, 269.83, 169.99] },
{ name: '浙江省', value: [120.1551, 30.2794, 8.94, 90.98] },
{ name: '广东省', value: [113.2644, 23.1291, 7.5, 149.76] },
{ name: '台湾省', value: [121.5090, 25.0443, 32.75, 117.88] },
{ name: '四川省', value: [104.0657, 30.6595, 1.01, 48.7] },
{ name: '江苏省', value: [118.7780, 32.0472, 5.25, 56.28] },
{ name: '湖北省', value: [114.3053, 30.5928, 1.84, 34.09] },
{ name: '福建省', value: [119.3062, 26.0747, 1.39, 16.79] },
{ name: '陕西省', value: [108.9480, 34.2632, 1.04, 21.38] },
{ name: '重庆市', value: [106.5049, 29.5332, 1.61, 13.24] },
{ name: '山东省', value: [120.3331, 36.0670, 1.02, 16.02] },
{ name: '湖南省', value: [112.9822, 28.1944, 0.68, 14.25] },
{ name: '安徽省', value: [117.2830, 31.8611, 0.93, 12.92] },
{ name: '辽宁省', value: [123.4321, 41.8007, 0.77, 11.37] },
{ name: '云南省', value: [102.7123, 25.0406, 0.1, 3.68] },
{ name: '天津市', value: [117.1901, 39.1258, 7.79, 9.34] },
{ name: '河南省', value: [113.6654, 34.7579, 0.55, 9.07] },
{ name: '吉林省', value: [125.3245, 43.8171, 0.15, 2.7] },
{ name: '江西省', value: [115.8581, 28.6820, 0.25, 4.17] },
{ name: '广西壮族自治区', value: [108.3200, 22.8241, 0.14, 3.15] },
{ name: '黑龙江省', value: [126.5349, 45.8038, 0.11, 5.13] },
{ name: '澳门特别行政区', value: [113.5490, 22.1987, 737.5, 2.36] },
{ name: '河北省', value: [114.5024, 38.0454, 0.26, 4.74] },
{ name: '山西省', value: [112.5489, 37.8570, 0.25, 3.78] },
{ name: '贵州省', value: [106.7135, 26.5783, 0.11, 1.93] },
{ name: '内蒙古自治区', value: [111.6708, 40.8183, 0.01, 0.96] },
{ name: '香港特别行政区', value: [114.1095, 22.3964, 1797.7, 197.75] },
{ name: '新疆维吾尔自治区', value: [87.6168, 43.7928, 0.01, 0.77] },
{ name: '甘肃省', value: [103.8235, 36.0580, 0.04, 1.46] },
{ name: '海南省', value: [110.3312, 20.0319, 0.36, 1.25] },
{ name: '宁夏回族自治区', value: [106.2782, 38.4664, 0.05, 0.27] },
{ name: '西藏自治区', value: [91.1175, 29.6469, 0.01, 0.14] },
{ name: '青海省', value: [101.7782, 36.6172, 0.01, 0.18] }
];
} finally {
loading.value = false;
}
};
// 计算 TOP 5 地区
const topRegions = computed(() => {
const sorted = [...talentMapData.value]
.map(item => ({
name: item.name,
score: item.value[3],
density: item.value[2]
}))
.sort((a, b) => b.score - a.score);
return sorted.slice(0, 5);
});
// 组件挂载时获取数据
onMounted(() => {
fetchDataFromApi();
// 数据加载完成后初始化图表
setTimeout(() => {
initTalentMapChart()
}, 100);
});
const initTalentMapChart = () => {
const el = document.getElementById('talentMapChart');
if (!el) return;
if (talentMapChart.value) {
talentMapChart.value.dispose();
}
const myChart = echarts.init(el);
talentMapChart.value = myChart;
// --- 修改后的加载逻辑:直接获取 JSON ---
// 这里可以换成你本地项目的路径,例如:'/maps/china.json'
const MAP_JSON_URL = 'https://geo.datav.aliyun.com/areas_v3/bound/100000_full.json';
// 1. 注册地图数据
echarts.registerMap('china', geoJson);
// 2. 调用渲染函数
renderMap();
// 渲染地图的函数 (配置保持原有的科技感风格)
function renderMap() {
myChart.setOption({
tooltip: {
trigger: 'item',
backgroundColor: 'rgba(17, 54, 122, 0.95)',
borderColor: '#00A0FF',
borderWidth: 1,
borderRadius: 8,
padding: 16,
textStyle: { color: '#6dd0e3', fontSize: 14 },
formatter: (params: any) => {
if (params.seriesType === 'scatter') {
const data = params.data.value;
const density = data[2];
const total = data[3];
// 内部辅助函数获取等级
const getLevel = (d: number) => {
if(d > 500) return '人才极高密度区';
if(d > 100) return '人才高密度区';
return '人才稳步增长区';
};
return `
<div style="line-height: 2;">
<div style="font-weight: bold; color: #00F2FF; font-size: 16px; margin-bottom: 10px;">${params.name}</div>
<div><span style="color: #6dd0e3;">人才密度:</span><span style="color: #00C0FF; font-weight: 700;">${density}</span> 人/km²</div>
<div><span style="color: #6dd0e3;">人才总量:</span><span style="color: #00C0FF; font-weight: 700;">${total}</span> 万人</div>
<div style="margin-top: 8px; padding: 4px 10px; background: rgba(0, 242, 255, 0.2); border-radius: 6px; color: #00F2FF; font-size: 12px;">${getLevel(density)}</div>
</div>
`;
}
return '';
}
},
visualMap: {
show: false,
min: 0,
max: 500,
inRange: {
color: ['#00A0FF', '#0063D0', '#004190'],
symbolSize: [10, 60]
}
},
geo: {
map: 'china',
roam: true,
scaleLimit: { min: 0.8, max: 4 },
zoom: 2.6, // 调整为 standard zoom
center: [104.5, 37.5],
label: {
show: true,
fontSize: 10,
color: '#CEF4FD'
},
itemStyle: {
areaColor: 'rgba(0, 30, 80, 0.6)',
borderColor: 'rgba(0, 160, 255, 0.5)',
borderWidth: 1.5,
shadowBlur: 10,
shadowColor: 'rgba(0, 160, 255, 0.3)'
},
emphasis: {
itemStyle: {
areaColor: 'rgba(0, 50, 120, 0.8)',
borderColor: '#00C0FF'
}
}
},
series: [
{
name: '开源人才密度',
type: 'scatter',
coordinateSystem: 'geo',
data: talentMapData.value,
symbolSize: (val: any) => Math.sqrt(val[2]) * 2 + 8,
itemStyle: {
color: (params: any) => {
const density = params.value[2];
if (density >= 500) return '#FF4900';
if (density >= 100) return '#FFAB00';
return '#00C0FF';
},
shadowBlur: 20,
shadowColor: 'rgba(0, 242, 255, 0.6)'
}
}
]
});
}
};
</script>
<style lang="scss" scoped>
.num-empty {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
color: #999;
}
.map-container {
display: flex;
gap: 2rem;
align-items: stretch;
position: relative;
min-height: 600px;
}
.map-container .chart {
flex: 1;
min-height: 600px;
background: linear-gradient(135deg,
rgba(0, 20, 60, 0.4) 0%,
rgba(0, 40, 100, 0.3) 100%);
border-radius: 1rem;
border: 2px solid rgba(0, 192, 255, 0.3);
box-shadow: 0 8px 24px rgba(0, 192, 255, 0.2);
padding: 1rem;
}
.map-rank-panel {
width: 300px;
border-radius: 1rem;
border: 2px solid rgba(0, 192, 255, 0.3);
padding: 1.5rem;
box-shadow: 0 8px 32px rgba(0, 192, 255, 0.2);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
display: flex;
flex-direction: column;
gap: 1rem;
}
.panel-title {
font-size: 1.2rem;
font-weight: 700;
color: black;
text-align: center;
padding-bottom: 1rem;
margin-bottom: 0.5rem;
border-bottom: 2px solid rgba(0, 242, 255, 0.3);
}
.rank-list {
display: flex;
flex-direction: column;
gap: 0.75rem;
max-height: 400px;
overflow-y: auto;
padding-right: 5px;
}
.rank-item {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.85rem 1rem;
background: rgba(0, 192, 255, 0.08);
border-radius: 0.75rem;
border: 1px solid rgba(0, 192, 255, 0.2);
border-left: 3px solid #00A0FF;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
overflow: hidden;
margin-bottom: 0.75rem;
}
.rank-item::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(0, 242, 255, 0.15), transparent);
transition: left 0.5s;
}
.rank-item:hover::before {
left: 100%;
}
.rank-item:hover {
background: rgba(0, 192, 255, 0.2);
border-color: #00F2FF;
border-left-color: #00F2FF;
box-shadow: 0 6px 16px rgba(0, 242, 255, 0.3);
transform: translateX(5px) scale(1.02);
}
.rank-num {
width: 32px;
height: 32px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
font-weight: 700;
flex-shrink: 0;
font-family: 'DIN Alternate', 'Arial', sans-serif;
}
.rank-num.rank-1 {
background: linear-gradient(135deg, #FFD700, #FFA500);
color: #1a1a1a;
box-shadow: 0 4px 12px rgba(255, 215, 0, 0.6);
border: 2px solid rgba(255, 255, 255, 0.3);
}
.rank-num.rank-2 {
background: linear-gradient(135deg, #E8E8E8, #C0C0C0);
color: #1a1a1a;
box-shadow: 0 4px 12px rgba(192, 192, 192, 0.6);
border: 2px solid rgba(255, 255, 255, 0.3);
}
.rank-num.rank-3 {
background: linear-gradient(135deg, #CD7F32, #B8733C);
color: white;
box-shadow: 0 4px 12px rgba(205, 127, 50, 0.6);
border: 2px solid rgba(255, 255, 255, 0.3);
}
.rank-num.rank-4,
.rank-num.rank-5 {
background: rgba(0, 192, 255, 0.2);
color: #00C0FF;
font-weight: bold;
border: 2px solid rgba(0, 192, 255, 0.4);
}
.rank-name {
flex: 1;
font-size: 14px;
font-weight: 600;
color: #000000;
}
.rank-score {
font-size: 14px;
font-weight: 700;
color: #00F2FF;
font-family: 'DIN Alternate', 'Arial', sans-serif;
text-shadow: 0 0 8px rgba(0, 242, 255, 0.5);
}
</style>