|
|
|
|
|
<!--
|
|
|
|
|
|
* @Author: chris
|
|
|
|
|
|
* @Date: 2025-08-25 15:51:13
|
|
|
|
|
|
* @LastEditors: chris
|
|
|
|
|
|
* @LastEditTime: 2025-09-04 11:04:43
|
|
|
|
|
|
-->
|
|
|
|
|
|
<script setup>
|
|
|
|
|
|
import ModuleTitle from './moduleTitle.vue';
|
|
|
|
|
|
import { View } from '@element-plus/icons-vue';
|
|
|
|
|
|
|
|
|
|
|
|
// 使用proxy访问全局$echarts
|
|
|
|
|
|
const { proxy } = getCurrentInstance();
|
|
|
|
|
|
const echarts = proxy?.$echarts;
|
|
|
|
|
|
|
|
|
|
|
|
const props = defineProps({
|
|
|
|
|
|
pestInfo: {
|
|
|
|
|
|
type: Object,
|
|
|
|
|
|
default: () => ({
|
|
|
|
|
|
// 默认模拟数据
|
|
|
|
|
|
current: {
|
|
|
|
|
|
totalCount: 156,
|
|
|
|
|
|
types: [
|
|
|
|
|
|
{ name: '蚜虫', count: 48, color: '#ff6b6b' },
|
|
|
|
|
|
{ name: '红蜘蛛', count: 36, color: '#ffa502' },
|
|
|
|
|
|
{ name: '果蝇', count: 24, color: '#5f27cd' },
|
|
|
|
|
|
{ name: '菜青虫', count: 18, color: '#10ac84' },
|
|
|
|
|
|
{ name: '其他', count: 30, color: '#54a0ff' }
|
|
|
|
|
|
]
|
|
|
|
|
|
},
|
|
|
|
|
|
trend: Array(7).fill().map((_, index) => {
|
|
|
|
|
|
const date = new Date();
|
|
|
|
|
|
date.setDate(date.getDate() - (6 - index));
|
|
|
|
|
|
return {
|
|
|
|
|
|
date: `${date.getMonth() + 1}/${date.getDate()}`,
|
|
|
|
|
|
count: 100 + Math.sin(index / 2) * 50 + Math.random() * 30
|
|
|
|
|
|
};
|
|
|
|
|
|
})
|
|
|
|
|
|
})
|
|
|
|
|
|
},
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const trendChartRef = ref(null);
|
|
|
|
|
|
const distributionChartRef = ref(null);
|
|
|
|
|
|
let trendChartInstance = null;
|
|
|
|
|
|
let distributionChartInstance = null;
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化趋势图
|
|
|
|
|
|
const initTrendChart = () => {
|
|
|
|
|
|
if (trendChartRef.value && echarts) {
|
|
|
|
|
|
// 销毁已存在的图表实例
|
|
|
|
|
|
if (trendChartInstance) {
|
|
|
|
|
|
trendChartInstance.dispose();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
trendChartInstance = echarts.init(trendChartRef.value);
|
|
|
|
|
|
updateTrendChart();
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化分布图
|
|
|
|
|
|
const initDistributionChart = () => {
|
|
|
|
|
|
if (distributionChartRef.value && echarts) {
|
|
|
|
|
|
// 销毁已存在的图表实例
|
|
|
|
|
|
if (distributionChartInstance) {
|
|
|
|
|
|
distributionChartInstance.dispose();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
distributionChartInstance = echarts.init(distributionChartRef.value);
|
|
|
|
|
|
updateDistributionChart();
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 更新趋势图数据
|
|
|
|
|
|
const updateTrendChart = () => {
|
|
|
|
|
|
if (!trendChartInstance || !echarts) return;
|
|
|
|
|
|
|
|
|
|
|
|
const gradient = new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
|
|
|
|
|
{ offset: 0, color: 'rgba(77, 171, 247, 0.5)' },
|
|
|
|
|
|
{ offset: 1, color: 'rgba(77, 171, 247, 0.1)' }
|
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
const option = {
|
|
|
|
|
|
title: {
|
|
|
|
|
|
text: '7天虫情数量趋势',
|
|
|
|
|
|
textStyle: {
|
|
|
|
|
|
color: '#fff',
|
|
|
|
|
|
fontSize: 16,
|
|
|
|
|
|
fontWeight: 'bold'
|
|
|
|
|
|
},
|
|
|
|
|
|
left: 'center'
|
|
|
|
|
|
},
|
|
|
|
|
|
tooltip: {
|
|
|
|
|
|
trigger: 'axis',
|
|
|
|
|
|
backgroundColor: 'rgba(0, 0, 0, 0.7)',
|
|
|
|
|
|
borderColor: '#333',
|
|
|
|
|
|
textStyle: {
|
|
|
|
|
|
color: '#fff'
|
|
|
|
|
|
},
|
|
|
|
|
|
formatter: function(params) {
|
|
|
|
|
|
const data = params[0];
|
|
|
|
|
|
return `${data.name}<br/>虫情数量: ${Math.round(data.value)}`;
|
|
|
|
|
|
},
|
|
|
|
|
|
axisPointer: {
|
|
|
|
|
|
type: 'cross',
|
|
|
|
|
|
label: {
|
|
|
|
|
|
backgroundColor: '#6a7985'
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
grid: {
|
|
|
|
|
|
left: '3%',
|
|
|
|
|
|
right: '4%',
|
|
|
|
|
|
bottom: '15%',
|
|
|
|
|
|
top: '15%',
|
|
|
|
|
|
containLabel: true
|
|
|
|
|
|
},
|
|
|
|
|
|
xAxis: {
|
|
|
|
|
|
type: 'category',
|
|
|
|
|
|
boundaryGap: false,
|
|
|
|
|
|
data: props.pestInfo.trend.map(item => item.date),
|
|
|
|
|
|
axisLine: {
|
|
|
|
|
|
lineStyle: {
|
|
|
|
|
|
color: '#464646'
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
axisLabel: {
|
|
|
|
|
|
color: '#ddd'
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
yAxis: [
|
|
|
|
|
|
{
|
|
|
|
|
|
type: 'value',
|
|
|
|
|
|
name: '数量',
|
|
|
|
|
|
nameTextStyle: {
|
|
|
|
|
|
color: '#ddd'
|
|
|
|
|
|
},
|
|
|
|
|
|
min: 0,
|
|
|
|
|
|
max: 'dataMax + 50',
|
|
|
|
|
|
axisLine: {
|
|
|
|
|
|
lineStyle: {
|
|
|
|
|
|
color: '#464646'
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
axisLabel: {
|
|
|
|
|
|
color: '#ddd'
|
|
|
|
|
|
},
|
|
|
|
|
|
splitLine: {
|
|
|
|
|
|
lineStyle: {
|
|
|
|
|
|
color: '#2a2a2a',
|
|
|
|
|
|
type: 'dashed'
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
],
|
|
|
|
|
|
series: [
|
|
|
|
|
|
{
|
|
|
|
|
|
name: '虫情数量',
|
|
|
|
|
|
type: 'line',
|
|
|
|
|
|
data: props.pestInfo.trend.map(item => Math.round(item.count)),
|
|
|
|
|
|
smooth: true,
|
|
|
|
|
|
symbol: 'circle',
|
|
|
|
|
|
symbolSize: 6,
|
|
|
|
|
|
itemStyle: {
|
|
|
|
|
|
color: '#4dabf7'
|
|
|
|
|
|
},
|
|
|
|
|
|
areaStyle: {
|
|
|
|
|
|
color: gradient
|
|
|
|
|
|
},
|
|
|
|
|
|
emphasis: {
|
|
|
|
|
|
focus: 'series'
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
trendChartInstance.setOption(option);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 更新分布图数据
|
|
|
|
|
|
const updateDistributionChart = () => {
|
|
|
|
|
|
if (!distributionChartInstance || !echarts) return;
|
|
|
|
|
|
|
|
|
|
|
|
const option = {
|
|
|
|
|
|
title: {
|
|
|
|
|
|
text: '虫情分布状况',
|
|
|
|
|
|
textStyle: {
|
|
|
|
|
|
color: '#fff',
|
|
|
|
|
|
fontSize: 16,
|
|
|
|
|
|
fontWeight: 'bold'
|
|
|
|
|
|
},
|
|
|
|
|
|
left: 'center'
|
|
|
|
|
|
},
|
|
|
|
|
|
tooltip: {
|
|
|
|
|
|
trigger: 'item',
|
|
|
|
|
|
backgroundColor: 'rgba(0, 0, 0, 0.7)',
|
|
|
|
|
|
borderColor: '#333',
|
|
|
|
|
|
textStyle: {
|
|
|
|
|
|
color: '#fff'
|
|
|
|
|
|
},
|
|
|
|
|
|
formatter: '{b}: {c} ({d}%)'
|
|
|
|
|
|
},
|
|
|
|
|
|
legend: {
|
|
|
|
|
|
orient: 'vertical',
|
|
|
|
|
|
right: '5%',
|
|
|
|
|
|
top: 'center',
|
|
|
|
|
|
textStyle: {
|
|
|
|
|
|
color: '#ddd'
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
series: [
|
|
|
|
|
|
{
|
|
|
|
|
|
name: '虫情分布',
|
|
|
|
|
|
type: 'pie',
|
|
|
|
|
|
radius: ['40%', '70%'],
|
|
|
|
|
|
center: ['35%', '50%'],
|
|
|
|
|
|
avoidLabelOverlap: false,
|
|
|
|
|
|
itemStyle: {
|
|
|
|
|
|
borderRadius: 10,
|
|
|
|
|
|
borderColor: '#1a1a1a',
|
|
|
|
|
|
borderWidth: 2
|
|
|
|
|
|
},
|
|
|
|
|
|
label: {
|
|
|
|
|
|
show: false,
|
|
|
|
|
|
position: 'center'
|
|
|
|
|
|
},
|
|
|
|
|
|
emphasis: {
|
|
|
|
|
|
label: {
|
|
|
|
|
|
show: true,
|
|
|
|
|
|
fontSize: '16',
|
|
|
|
|
|
fontWeight: 'bold',
|
|
|
|
|
|
color: '#fff'
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
labelLine: {
|
|
|
|
|
|
show: false
|
|
|
|
|
|
},
|
|
|
|
|
|
data: props.pestInfo.current.types.map(type => ({
|
|
|
|
|
|
name: type.name,
|
|
|
|
|
|
value: type.count,
|
|
|
|
|
|
itemStyle: {
|
|
|
|
|
|
color: type.color
|
|
|
|
|
|
}
|
|
|
|
|
|
}))
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
distributionChartInstance.setOption(option);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 监听数据变化
|
|
|
|
|
|
watch(() => props.pestInfo, () => {
|
|
|
|
|
|
updateTrendChart();
|
|
|
|
|
|
updateDistributionChart();
|
|
|
|
|
|
}, { deep: true });
|
|
|
|
|
|
|
|
|
|
|
|
// 响应窗口大小变化
|
|
|
|
|
|
const handleResize = () => {
|
|
|
|
|
|
if (trendChartInstance) {
|
|
|
|
|
|
trendChartInstance.resize();
|
|
|
|
|
|
}
|
|
|
|
|
|
if (distributionChartInstance) {
|
|
|
|
|
|
distributionChartInstance.resize();
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
|
initTrendChart();
|
|
|
|
|
|
initDistributionChart();
|
|
|
|
|
|
window.addEventListener('resize', handleResize);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 组件卸载时清理资源
|
|
|
|
|
|
const onUnmounted = () => {
|
|
|
|
|
|
window.removeEventListener('resize', handleResize);
|
|
|
|
|
|
if (trendChartInstance) {
|
|
|
|
|
|
trendChartInstance.dispose();
|
|
|
|
|
|
}
|
|
|
|
|
|
if (distributionChartInstance) {
|
|
|
|
|
|
distributionChartInstance.dispose();
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<template>
|
|
|
|
|
|
<div class="pest-info">
|
|
|
|
|
|
<!-- <div class="info-header">
|
|
|
|
|
|
<div class="title">
|
|
|
|
|
|
<View class="title-icon" />
|
|
|
|
|
|
<h3 class="main-title">虫情监测</h3>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="total-count">
|
|
|
|
|
|
<span class="total-label">总虫情数:</span>
|
|
|
|
|
|
<span class="total-value">{{ pestInfo.current.totalCount }}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div> -->
|
|
|
|
|
|
<module-title title="虫情监测" icon="bug">
|
|
|
|
|
|
<div class="total-count">
|
|
|
|
|
|
<span class="total-label">总虫情数:</span>
|
|
|
|
|
|
<span class="total-value">{{ pestInfo.current.totalCount }}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</module-title>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 左中右三列布局 -->
|
|
|
|
|
|
<div class="main-content">
|
|
|
|
|
|
<!-- 左列:7天虫情数量趋势图 -->
|
|
|
|
|
|
<div class="left-column">
|
|
|
|
|
|
<div class="chart-card">
|
|
|
|
|
|
<div ref="trendChartRef" class="trend-chart"></div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 中列:虫情分布状况图 -->
|
|
|
|
|
|
<div class="middle-column">
|
|
|
|
|
|
<div class="chart-card">
|
|
|
|
|
|
<div ref="distributionChartRef" class="distribution-chart"></div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 右列:害虫数量部分模块 -->
|
|
|
|
|
|
<div class="right-column">
|
|
|
|
|
|
<div class="pest-types-card">
|
|
|
|
|
|
<div class="pest-types-title">害虫类型数量</div>
|
|
|
|
|
|
<div class="pest-types">
|
|
|
|
|
|
<div v-for="type in pestInfo.current.types" :key="type.name" class="type-item">
|
|
|
|
|
|
<div class="color-dot" :style="{ backgroundColor: type.color }" />
|
|
|
|
|
|
<span class="type-name">{{ type.name }}</span>
|
|
|
|
|
|
<span class="type-count">{{ type.count }}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
|
|
// 定义SCSS变量
|
|
|
|
|
|
$primary-color: #4dabf7;
|
|
|
|
|
|
$hover-bg: rgba(255, 255, 255, 0.1);
|
|
|
|
|
|
$border-radius: 12px;
|
|
|
|
|
|
$transition: all 0.3s ease;
|
|
|
|
|
|
|
|
|
|
|
|
// 使用UnoCSS的@apply语法
|
|
|
|
|
|
.pest-info {
|
|
|
|
|
|
@apply rounded-xl p-5 shadow-lg flex flex-col gap-5;
|
|
|
|
|
|
// background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.info-header {
|
|
|
|
|
|
@apply flex items-center justify-between;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.title {
|
|
|
|
|
|
@apply flex items-center gap-2;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 主标题样式统一
|
|
|
|
|
|
.title-icon {
|
|
|
|
|
|
@apply text-primary w-6 h-6;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.main-title {
|
|
|
|
|
|
@apply text-lg font-bold text-white m-0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.total-count {
|
|
|
|
|
|
@apply flex items-center px-4;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.total-label {
|
|
|
|
|
|
@apply text-sm text-[#a0a0a0];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.total-value {
|
|
|
|
|
|
@apply text-xl font-bold text-primary ml-1 color-rose;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 左中右三列布局
|
|
|
|
|
|
.main-content {
|
|
|
|
|
|
@apply flex gap-3 flex-1 h-full overflow-hidden;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 左列样式
|
|
|
|
|
|
.left-column {
|
|
|
|
|
|
@apply w-[43%];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 中列样式
|
|
|
|
|
|
.middle-column {
|
|
|
|
|
|
@apply w-[32%];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 右列样式
|
|
|
|
|
|
.right-column {
|
|
|
|
|
|
@apply w-[25%];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.chart-card {
|
|
|
|
|
|
@apply bg-[rgba(255,255,255,0.03)] rounded-xl p-5 border border-[rgba(255,255,255,0.1)] h-full;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.trend-chart,
|
|
|
|
|
|
.distribution-chart {
|
|
|
|
|
|
@apply w-full h-full min-h-[280px];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 右侧害虫数量卡片样式
|
|
|
|
|
|
.pest-types-card {
|
|
|
|
|
|
@apply bg-[rgba(255,255,255,0.03)] rounded-xl p-3 border border-[rgba(255,255,255,0.1)] h-full flex flex-col;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.pest-types-title {
|
|
|
|
|
|
@apply text-base font-bold text-white mb-2;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.pest-types {
|
|
|
|
|
|
@apply flex flex-col flex-1 space-y-1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.type-item {
|
|
|
|
|
|
@apply flex items-center p-1.5 hover:bg-[rgba(255,255,255,0.05)] rounded-lg transition-all duration-300;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.color-dot {
|
|
|
|
|
|
@apply w-3 h-3 rounded-full mr-2;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.type-name {
|
|
|
|
|
|
@apply text-xs text-[#ddd] flex-1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.type-count {
|
|
|
|
|
|
@apply text-sm font-bold text-white;
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|