2026-01-26 15:40:52 +08:00
|
|
|
|
<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';
|
2026-01-26 17:23:49 +08:00
|
|
|
|
import { industryDistributionPage1 } from '@/api/jyh/situation';
|
2026-01-26 15:40:52 +08:00
|
|
|
|
|
|
|
|
|
|
// 定义数据类型
|
|
|
|
|
|
interface IndustryData {
|
|
|
|
|
|
value: number;
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 定义配色方案
|
|
|
|
|
|
const industryColorPalette = [
|
|
|
|
|
|
'#00f0ff', '#00b03e', '#ffeb00', '#ffab00',
|
|
|
|
|
|
'#ff5100', '#c0232a', '#a262f2', '#379bff'
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
// 内部数据
|
2026-01-26 17:23:49 +08:00
|
|
|
|
const internalData = ref<IndustryData[]>([]);
|
2026-01-26 15:40:52 +08:00
|
|
|
|
|
|
|
|
|
|
// 响应式数据
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
2026-01-30 14:33:06 +08:00
|
|
|
|
// 构建图例数据,对于较长的名称才在图例中显示数值
|
|
|
|
|
|
const legendData = internalData.value.map(item => ({
|
|
|
|
|
|
name: item.name,
|
|
|
|
|
|
icon: 'circle'
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
2026-01-26 15:40:52 +08:00
|
|
|
|
const option: echarts.EChartsOption = {
|
|
|
|
|
|
tooltip: {
|
|
|
|
|
|
trigger: 'item',
|
2026-01-30 14:33:06 +08:00
|
|
|
|
formatter: (params: any) => `${params.name}: ${params.value} (${params.percent}%)`
|
2026-01-26 15:40:52 +08:00
|
|
|
|
},
|
|
|
|
|
|
legend: {
|
|
|
|
|
|
bottom: '0',
|
2026-01-30 14:33:06 +08:00
|
|
|
|
textStyle: { fontSize: 10 },
|
2026-01-26 15:40:52 +08:00
|
|
|
|
itemWidth: 10,
|
2026-01-30 14:33:06 +08:00
|
|
|
|
itemHeight: 10,
|
|
|
|
|
|
orient: 'horizontal',
|
|
|
|
|
|
padding: [5, 0, 0, 0],
|
|
|
|
|
|
data: legendData
|
2026-01-26 15:40:52 +08:00
|
|
|
|
},
|
|
|
|
|
|
series: [
|
|
|
|
|
|
{
|
|
|
|
|
|
name: '行业分布',
|
|
|
|
|
|
type: 'pie',
|
|
|
|
|
|
radius: ['40%', '70%'],
|
2026-02-02 14:14:04 +08:00
|
|
|
|
center: ['50%', '45%'],
|
2026-01-26 15:40:52 +08:00
|
|
|
|
data: internalData.value,
|
|
|
|
|
|
label: {
|
|
|
|
|
|
show: true,
|
2026-01-30 14:33:06 +08:00
|
|
|
|
formatter: '{b}\n{d}%',
|
|
|
|
|
|
fontSize: 10,
|
|
|
|
|
|
rich: {
|
|
|
|
|
|
b: {
|
|
|
|
|
|
fontSize: 10,
|
|
|
|
|
|
lineHeight: 12
|
|
|
|
|
|
},
|
|
|
|
|
|
d: {
|
|
|
|
|
|
fontSize: 10,
|
|
|
|
|
|
lineHeight: 12
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-01-26 15:40:52 +08:00
|
|
|
|
},
|
|
|
|
|
|
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();
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-01-26 17:23:49 +08:00
|
|
|
|
// 获取行业分布数据
|
|
|
|
|
|
const fetchIndustryData = async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await industryDistributionPage1();
|
2026-01-30 10:22:54 +08:00
|
|
|
|
const businessData = response.data; // Axios响应格式,业务数据在response.data中
|
|
|
|
|
|
|
|
|
|
|
|
if (businessData.code === 200 && businessData.data) {
|
|
|
|
|
|
internalData.value = businessData.data;
|
2026-01-26 17:23:49 +08:00
|
|
|
|
} else {
|
2026-01-30 10:22:54 +08:00
|
|
|
|
console.error('获取行业分布数据失败:', businessData.msg || '未知错误');
|
2026-01-26 17:23:49 +08:00
|
|
|
|
internalData.value = [
|
|
|
|
|
|
{ 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: '移动开发' }
|
|
|
|
|
|
];
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('请求行业分布数据时发生错误:', error);
|
|
|
|
|
|
internalData.value = [
|
|
|
|
|
|
{ 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: '移动开发' }
|
|
|
|
|
|
];
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-01-26 15:40:52 +08:00
|
|
|
|
// 生命周期钩子
|
2026-01-26 17:23:49 +08:00
|
|
|
|
onMounted(async () => {
|
|
|
|
|
|
await fetchIndustryData(); // 获取行业分布数据
|
|
|
|
|
|
await nextTick();
|
|
|
|
|
|
initChart();
|
2026-01-26 15:40:52 +08:00
|
|
|
|
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>
|