318 lines
6.6 KiB
Vue
318 lines
6.6 KiB
Vue
<script setup>
|
|
import { ref, onMounted, onUnmounted } from 'vue'
|
|
import * as echarts from 'echarts'
|
|
import request from '@/utils/request'
|
|
|
|
let speciesChart = null
|
|
let patrolChart = null
|
|
const speciesChartRef = ref(null)
|
|
const patrolChartRef = ref(null)
|
|
|
|
// 获取物种统计数据
|
|
const fetchSpeciesData = async () => {
|
|
try {
|
|
const res = await request.get('/api/admin/species/statistics/overview')
|
|
if (res.success && res.data) {
|
|
updateSpeciesChart(res.data)
|
|
}
|
|
} catch (error) {
|
|
console.error('获取物种统计数据失败:', error)
|
|
}
|
|
}
|
|
|
|
// 获取巡护统计数据
|
|
const fetchPatrolData = async () => {
|
|
try {
|
|
const res = await request.get('/api/admin/patrol/records/statistics/overview')
|
|
if (res.success && res.data) {
|
|
updatePatrolChart(res.data)
|
|
}
|
|
} catch (error) {
|
|
console.error('获取巡护统计数据失败:', error)
|
|
}
|
|
}
|
|
|
|
// 初始化物种统计图表
|
|
const initSpeciesChart = () => {
|
|
if (!speciesChartRef.value) return
|
|
|
|
speciesChart = echarts.init(speciesChartRef.value)
|
|
const option = {
|
|
backgroundColor: 'transparent',
|
|
title: {
|
|
text: '物种分布统计',
|
|
textStyle: {
|
|
color: '#fff',
|
|
fontSize: 16
|
|
},
|
|
left: 'center',
|
|
top: 0
|
|
},
|
|
tooltip: {
|
|
trigger: 'item',
|
|
formatter: '{b}: {c} ({d}%)'
|
|
},
|
|
legend: {
|
|
orient: 'vertical',
|
|
right: '5%',
|
|
top: 'middle',
|
|
textStyle: {
|
|
color: '#fff'
|
|
}
|
|
},
|
|
series: [
|
|
{
|
|
type: 'pie',
|
|
radius: ['40%', '70%'],
|
|
center: ['40%', '55%'],
|
|
avoidLabelOverlap: true,
|
|
itemStyle: {
|
|
borderRadius: 10,
|
|
borderColor: '#fff',
|
|
borderWidth: 2
|
|
},
|
|
label: {
|
|
show: true,
|
|
color: '#fff',
|
|
formatter: '{b}\n{c}种'
|
|
},
|
|
emphasis: {
|
|
label: {
|
|
show: true,
|
|
fontSize: 16,
|
|
fontWeight: 'bold'
|
|
}
|
|
},
|
|
data: []
|
|
}
|
|
]
|
|
}
|
|
speciesChart.setOption(option)
|
|
}
|
|
|
|
// 初始化巡护统计图表
|
|
const initPatrolChart = () => {
|
|
if (!patrolChartRef.value) return
|
|
|
|
patrolChart = echarts.init(patrolChartRef.value)
|
|
const option = {
|
|
backgroundColor: 'transparent',
|
|
title: {
|
|
text: '巡护任务统计',
|
|
textStyle: {
|
|
color: '#fff',
|
|
fontSize: 16
|
|
},
|
|
left: 'center',
|
|
top: 0
|
|
},
|
|
tooltip: {
|
|
trigger: 'axis',
|
|
axisPointer: {
|
|
type: 'shadow'
|
|
}
|
|
},
|
|
grid: {
|
|
top: '15%',
|
|
right: '5%',
|
|
bottom: '5%',
|
|
left: '15%',
|
|
containLabel: true
|
|
},
|
|
xAxis: {
|
|
type: 'category',
|
|
data: ['已完成', '进行中', '未开始', '已超时'],
|
|
axisLine: {
|
|
lineStyle: {
|
|
color: 'rgba(255, 255, 255, 0.3)'
|
|
}
|
|
},
|
|
axisLabel: {
|
|
color: '#fff',
|
|
interval: 0
|
|
}
|
|
},
|
|
yAxis: {
|
|
type: 'value',
|
|
splitLine: {
|
|
lineStyle: {
|
|
color: 'rgba(255, 255, 255, 0.1)',
|
|
type: 'dashed'
|
|
}
|
|
},
|
|
axisLabel: {
|
|
color: '#fff'
|
|
}
|
|
},
|
|
series: [
|
|
{
|
|
type: 'bar',
|
|
barWidth: '40%',
|
|
itemStyle: {
|
|
borderRadius: [4, 4, 0, 0]
|
|
},
|
|
label: {
|
|
show: true,
|
|
position: 'top',
|
|
color: '#fff'
|
|
},
|
|
data: []
|
|
}
|
|
]
|
|
}
|
|
patrolChart.setOption(option)
|
|
}
|
|
|
|
// 更新物种统计图表
|
|
const updateSpeciesChart = (data) => {
|
|
const colors = {
|
|
'鸟类': '#36CFFF',
|
|
'鱼类': '#FFB72C',
|
|
'两栖类': '#4EF568',
|
|
'爬行类': '#FF36D9',
|
|
'哺乳类': '#9E87FF'
|
|
}
|
|
|
|
const chartData = Object.entries(data.category_counts || {}).map(([name, value]) => ({
|
|
name,
|
|
value,
|
|
itemStyle: {
|
|
color: colors[name] || '#36CFFF'
|
|
}
|
|
}))
|
|
|
|
speciesChart.setOption({
|
|
series: [{
|
|
data: chartData
|
|
}]
|
|
})
|
|
}
|
|
|
|
// 更新巡护统计图表
|
|
const updatePatrolChart = (data) => {
|
|
const colors = {
|
|
'已完成': '#67C23A',
|
|
'进行中': '#409EFF',
|
|
'未开始': '#909399',
|
|
'已超时': '#F56C6C'
|
|
}
|
|
|
|
const chartData = [
|
|
{ name: '已完成', value: data.completed || 0 },
|
|
{ name: '进行中', value: data.in_progress || 0 },
|
|
{ name: '未开始', value: data.not_started || 0 },
|
|
{ name: '已超时', value: data.overdue || 0 }
|
|
].map(item => ({
|
|
value: item.value,
|
|
itemStyle: {
|
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
|
{ offset: 0, color: colors[item.name] },
|
|
{ offset: 1, color: colors[item.name].replace('FF', '99') }
|
|
])
|
|
}
|
|
}))
|
|
|
|
patrolChart.setOption({
|
|
series: [{
|
|
data: chartData
|
|
}]
|
|
})
|
|
}
|
|
|
|
// 自动刷新数据
|
|
let timer = null
|
|
const startAutoRefresh = () => {
|
|
fetchSpeciesData()
|
|
fetchPatrolData()
|
|
timer = setInterval(() => {
|
|
fetchSpeciesData()
|
|
fetchPatrolData()
|
|
}, 60000) // 每分钟更新一次
|
|
}
|
|
|
|
// 处理窗口大小变化
|
|
const handleResize = () => {
|
|
speciesChart?.resize()
|
|
patrolChart?.resize()
|
|
}
|
|
|
|
onMounted(() => {
|
|
initSpeciesChart()
|
|
initPatrolChart()
|
|
startAutoRefresh()
|
|
window.addEventListener('resize', handleResize)
|
|
})
|
|
|
|
onUnmounted(() => {
|
|
if (timer) {
|
|
clearInterval(timer)
|
|
}
|
|
if (speciesChart) {
|
|
speciesChart.dispose()
|
|
speciesChart = null
|
|
}
|
|
if (patrolChart) {
|
|
patrolChart.dispose()
|
|
patrolChart = null
|
|
}
|
|
window.removeEventListener('resize', handleResize)
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<div class="data-chart">
|
|
<div class="chart-container">
|
|
<div class="chart-item">
|
|
<div class="chart-title">物种分布统计</div>
|
|
<div ref="speciesChartRef" class="species-chart"></div>
|
|
</div>
|
|
<div class="chart-item">
|
|
<div class="chart-title">巡护任务统计</div>
|
|
<div ref="patrolChartRef" class="patrol-chart"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style lang="scss" scoped>
|
|
.data-chart {
|
|
height: 100%;
|
|
padding: 16px;
|
|
box-sizing: border-box;
|
|
background: rgba(6, 30, 93, 0.5);
|
|
border-radius: 4px;
|
|
|
|
.chart-container {
|
|
height: 100%;
|
|
display: flex;
|
|
gap: 16px;
|
|
|
|
.chart-item {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
background: rgba(0, 0, 0, 0.2);
|
|
border-radius: 4px;
|
|
padding: 12px;
|
|
|
|
.chart-title {
|
|
font-size: 16px;
|
|
font-weight: bold;
|
|
color: #fff;
|
|
text-align: center;
|
|
margin-bottom: 12px;
|
|
background: linear-gradient(to bottom, #ffffff, #3fa7dd);
|
|
-webkit-background-clip: text;
|
|
color: transparent;
|
|
letter-spacing: 2px;
|
|
}
|
|
|
|
.species-chart,
|
|
.patrol-chart {
|
|
flex: 1;
|
|
min-height: 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
</style> |