|
|
<!--
|
|
|
* @Author: chris
|
|
|
* @Date: 2025-08-25 15:51:13
|
|
|
* @LastEditors: chris
|
|
|
* @LastEditTime: 2025-10-15 10:53:21
|
|
|
-->
|
|
|
<script setup>
|
|
|
import ModuleTitle from './moduleTitle.vue';
|
|
|
import { pestTypeDict } from '../config';
|
|
|
|
|
|
// 使用proxy访问全局$echarts
|
|
|
const { proxy } = getCurrentInstance();
|
|
|
const echarts = proxy?.$echarts;
|
|
|
|
|
|
const props = defineProps({
|
|
|
pestData: {
|
|
|
type: Array,
|
|
|
default: () => []
|
|
|
},
|
|
|
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 formateLineData = ref({})
|
|
|
const formatePieData = ref({})
|
|
|
const lineChartData = ref({})
|
|
|
const pieChartData = ref({})
|
|
|
|
|
|
const pestTotal = computed(() => {
|
|
|
return pieChartData.value.values?.reduce((total, cur) => total + cur, 0) || 0
|
|
|
})
|
|
|
|
|
|
watch(() => props.pestData, () => {
|
|
|
const { lineRes, pieRes } = formatData(props.pestData)
|
|
|
formateLineData.value = lineRes
|
|
|
formatePieData.value = pieRes
|
|
|
lineChartData.value = createChartData(formateLineData.value, 'line')
|
|
|
pieChartData.value = createChartData(formatePieData.value, 'pie')
|
|
|
updateTrendChart();
|
|
|
updateDistributionChart();
|
|
|
})
|
|
|
|
|
|
onMounted(() => {
|
|
|
console.log(proxy.$utils)
|
|
|
initTrendChart();
|
|
|
initDistributionChart();
|
|
|
window.addEventListener('resize', handleResize);
|
|
|
});
|
|
|
|
|
|
// 组件卸载时清理资源
|
|
|
onUnmounted(() => {
|
|
|
window.removeEventListener('resize', handleResize);
|
|
|
if (trendChartInstance) {
|
|
|
trendChartInstance.dispose();
|
|
|
}
|
|
|
if (distributionChartInstance) {
|
|
|
distributionChartInstance.dispose();
|
|
|
}
|
|
|
});
|
|
|
|
|
|
// 初始化趋势图
|
|
|
function initTrendChart () {
|
|
|
if (trendChartRef.value && echarts) {
|
|
|
// 销毁已存在的图表实例
|
|
|
if (trendChartInstance) {
|
|
|
trendChartInstance.dispose();
|
|
|
}
|
|
|
|
|
|
trendChartInstance = echarts.init(trendChartRef.value);
|
|
|
updateTrendChart();
|
|
|
}
|
|
|
};
|
|
|
|
|
|
// 初始化分布图
|
|
|
function initDistributionChart () {
|
|
|
if (distributionChartRef.value && echarts) {
|
|
|
// 销毁已存在的图表实例
|
|
|
if (distributionChartInstance) {
|
|
|
distributionChartInstance.dispose();
|
|
|
}
|
|
|
|
|
|
distributionChartInstance = echarts.init(distributionChartRef.value);
|
|
|
updateDistributionChart();
|
|
|
}
|
|
|
};
|
|
|
|
|
|
// 更新趋势图数据
|
|
|
function 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: lineChartData.value.dates?.map(item => {
|
|
|
const arr = item.split('/')
|
|
|
return `${arr[1]}/${arr[2]}日`
|
|
|
}) || [],
|
|
|
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: lineChartData.value.values || [],
|
|
|
smooth: true,
|
|
|
symbol: 'circle',
|
|
|
symbolSize: 6,
|
|
|
itemStyle: {
|
|
|
color: '#4dabf7'
|
|
|
},
|
|
|
areaStyle: {
|
|
|
color: gradient
|
|
|
},
|
|
|
emphasis: {
|
|
|
focus: 'series'
|
|
|
}
|
|
|
}
|
|
|
]
|
|
|
};
|
|
|
|
|
|
trendChartInstance.setOption(option);
|
|
|
};
|
|
|
|
|
|
// 更新分布图数据
|
|
|
function 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: pieChartData.value.names?.map((name, index) => ({
|
|
|
name,
|
|
|
value: pieChartData.value.values?.[index] || 0,
|
|
|
itemStyle: {
|
|
|
color: pestTypeDict[name]?.color || proxy.$utils.getRandomColor()
|
|
|
}
|
|
|
}))
|
|
|
}
|
|
|
]
|
|
|
};
|
|
|
|
|
|
distributionChartInstance.setOption(option);
|
|
|
};
|
|
|
|
|
|
// 响应窗口大小变化
|
|
|
function handleResize () {
|
|
|
if (trendChartInstance) {
|
|
|
trendChartInstance.resize();
|
|
|
}
|
|
|
if (distributionChartInstance) {
|
|
|
distributionChartInstance.resize();
|
|
|
}
|
|
|
};
|
|
|
|
|
|
function formatData (data) {
|
|
|
const lineRes = {}
|
|
|
const pieRes = {}
|
|
|
data.forEach(item => {
|
|
|
const date = item.createTime.split(' ')[0].replace(/-/g, '/')
|
|
|
lineRes[item.name] ? lineRes[item.name].push(item) : (lineRes[item.name] = [item])
|
|
|
pieRes[date] ? pieRes[date].push(item) : (pieRes[date] = [item])
|
|
|
})
|
|
|
return {
|
|
|
lineRes,
|
|
|
pieRes
|
|
|
}
|
|
|
}
|
|
|
|
|
|
function createChartData(data, type) {
|
|
|
const keys = Object.keys(data)
|
|
|
const values = []
|
|
|
names.forEach(key => {
|
|
|
values.push(data[key].reduce((pre, cur) => cur + (pre.value || 0), 0))
|
|
|
})
|
|
|
return type === 'line' ? { dates: keys, values } : { names: keys, values }
|
|
|
}
|
|
|
|
|
|
</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">{{ pestTotal }}</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="(name, index) in pieChartData.names" :key="name" class="type-item">
|
|
|
<div class="color-dot" :style="{ backgroundColor: pestTypeDict[name]?.color || proxy.$utils.getRandomColor()}" />
|
|
|
<span class="type-name">{{ name }}</span>
|
|
|
<span class="type-count">{{ pieChartData.values[index] || 0 }}</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-[45%];
|
|
|
}
|
|
|
|
|
|
// 中列样式
|
|
|
.middle-column {
|
|
|
@apply w-[33%];
|
|
|
}
|
|
|
|
|
|
// 右列样式
|
|
|
.right-column {
|
|
|
@apply w-[20%];
|
|
|
}
|
|
|
|
|
|
.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>
|