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

2 months ago
<!--
* @Author: chris
* @Date: 2025-08-25 15:51:13
* @LastEditors: chris
* @LastEditTime: 2025-10-15 10:53:21
2 months ago
-->
<script setup>
import ModuleTitle from './moduleTitle.vue';
import { pestTypeDict } from '../config';
2 months ago
// 使用proxy访问全局$echarts
const { proxy } = getCurrentInstance();
const echarts = proxy?.$echarts;
const props = defineProps({
pestData: {
type: Array,
default: () => []
},
2 months ago
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();
}
});
2 months ago
// 初始化趋势图
function initTrendChart () {
2 months ago
if (trendChartRef.value && echarts) {
// 销毁已存在的图表实例
if (trendChartInstance) {
trendChartInstance.dispose();
}
trendChartInstance = echarts.init(trendChartRef.value);
updateTrendChart();
}
};
// 初始化分布图
function initDistributionChart () {
2 months ago
if (distributionChartRef.value && echarts) {
// 销毁已存在的图表实例
if (distributionChartInstance) {
distributionChartInstance.dispose();
}
distributionChartInstance = echarts.init(distributionChartRef.value);
updateDistributionChart();
}
};
// 更新趋势图数据
function updateTrendChart () {
2 months ago
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]}`
}) || [],
2 months ago
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 || [],
2 months ago
smooth: true,
symbol: 'circle',
symbolSize: 6,
itemStyle: {
color: '#4dabf7'
},
areaStyle: {
color: gradient
},
emphasis: {
focus: 'series'
}
}
]
};
trendChartInstance.setOption(option);
};
// 更新分布图数据
function updateDistributionChart () {
2 months ago
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,
2 months ago
itemStyle: {
color: pestTypeDict[name]?.color || proxy.$utils.getRandomColor()
2 months ago
}
}))
}
]
};
distributionChartInstance.setOption(option);
};
// 响应窗口大小变化
function handleResize () {
2 months ago
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
2 months ago
}
}
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 }
}
2 months ago
</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>
2 months ago
</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>
2 months ago
</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%];
2 months ago
}
// 中列样式
.middle-column {
@apply w-[33%];
2 months ago
}
// 右列样式
.right-column {
@apply w-[20%];
2 months ago
}
.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>