You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

482 lines
11 KiB
Vue

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<!--
* @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>