Refactor monitoring API and dashboard charts with improved data handling and visualization
This commit is contained in:
parent
1265cf7553
commit
4a5e37b5d9
@ -3,35 +3,40 @@ import request from '@/utils/request'
|
||||
/**
|
||||
* 按时间范围查询监测数据
|
||||
* @param {Object} params - 查询参数
|
||||
* @param {string} params.start_date - 开始时间
|
||||
* @param {string} params.end_date - 结束时间
|
||||
* @param {Array} params.indicator_ids - 指标ID数组
|
||||
* @param {number} params.point_id - 监测点ID
|
||||
* @param {number} params.device_id - 设备ID
|
||||
* @param {string} [params.start_date] - 开始时间,格式:YYYY-MM-DD
|
||||
* @param {string} [params.end_date] - 结束时间,格式:YYYY-MM-DD
|
||||
* @param {string} [params.indicator_ids] - 指标ID,多个用逗号分隔
|
||||
* @param {string} [params.point_id] - 监测点ID
|
||||
* @param {string} [params.device_id] - 设备ID
|
||||
*/
|
||||
export function getMonitoringData(params) {
|
||||
return request.get('/api/monitoring/data', { params })
|
||||
return request.get('/api/monitoring/dataQuery', { params })
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取最新数据
|
||||
* @param {Object} params - 查询参数
|
||||
* @param {number} params.point_id - 监测点ID
|
||||
* @param {Array} params.indicator_ids - 指标ID数组
|
||||
* @param {number} params.device_id - 设备ID
|
||||
* @param {string} [params.point_id] - 监测点ID
|
||||
* @param {string} [params.indicator_ids] - 指标ID,多个用逗号分隔
|
||||
* @param {string} [params.device_id] - 设备ID
|
||||
*/
|
||||
export function getLatestData(params) {
|
||||
return request.get('/api/monitoring/data/latest', { params })
|
||||
// 确保 indicator_ids 是字符串格式
|
||||
const formattedParams = {
|
||||
...params,
|
||||
indicator_ids: Array.isArray(params.indicator_ids) ? params.indicator_ids.join(',') : params.indicator_ids
|
||||
}
|
||||
return request.get('/api/monitoring/data/latest', { params: formattedParams })
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取统计数据
|
||||
* @param {Object} params - 查询参数
|
||||
* @param {string} params.start_date - 开始时间
|
||||
* @param {string} params.end_date - 结束时间
|
||||
* @param {number} params.indicator_id - 指标ID
|
||||
* @param {number} params.point_id - 监测点ID
|
||||
* @param {number} params.device_id - 设备ID
|
||||
* @param {string} [params.start_date] - 开始时间,格式:YYYY-MM-DD
|
||||
* @param {string} [params.end_date] - 结束时间,格式:YYYY-MM-DD
|
||||
* @param {string} [params.indicator_id] - 指标ID
|
||||
* @param {string} [params.point_id] - 监测点ID
|
||||
* @param {string} [params.device_id] - 设备ID
|
||||
*/
|
||||
export function getStatistics(params) {
|
||||
return request.get('/api/monitoring/data/statistics', { params })
|
||||
@ -40,9 +45,9 @@ export function getStatistics(params) {
|
||||
/**
|
||||
* 获取数据质量统计
|
||||
* @param {Object} params - 查询参数
|
||||
* @param {string} params.start_date - 开始时间
|
||||
* @param {string} params.end_date - 结束时间
|
||||
* @param {number} params.point_id - 监测点ID
|
||||
* @param {string} [params.start_date] - 开始时间,格式:YYYY-MM-DD
|
||||
* @param {string} [params.end_date] - 结束时间,格式:YYYY-MM-DD
|
||||
* @param {string} [params.point_id] - 监测点ID
|
||||
*/
|
||||
export function getQualityStatistics(params) {
|
||||
return request.get('/api/monitoring/data/quality-statistics', { params })
|
||||
|
@ -13,19 +13,109 @@ const fetchSpeciesData = async () => {
|
||||
try {
|
||||
const res = await request.get('/api/admin/species/statistics/overview')
|
||||
if (res.success && res.data) {
|
||||
updateSpeciesChart(res.data)
|
||||
// 中文名称映射
|
||||
const categoryNames = {
|
||||
bird: '鸟类',
|
||||
mammal: '哺乳类',
|
||||
fish: '鱼类',
|
||||
amphibian: '两栖类',
|
||||
reptile: '爬行类',
|
||||
insect: '昆虫类',
|
||||
plant: '植物'
|
||||
}
|
||||
|
||||
// 将数据转换为饼图所需格式
|
||||
const chartData = Object.entries(res.data.categories).map(([key, value]) => ({
|
||||
name: categoryNames[key],
|
||||
value: parseInt(value.total_count),
|
||||
itemStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: getSpeciesColor(key)[0] },
|
||||
{ offset: 1, color: getSpeciesColor(key)[1] }
|
||||
])
|
||||
}
|
||||
})).sort((a, b) => b.value - a.value) // 按数量从大到小排序
|
||||
|
||||
speciesChart.setOption({
|
||||
series: [{
|
||||
data: chartData
|
||||
}]
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取物种统计数据失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 获取物种类别的颜色
|
||||
const getSpeciesColor = (category) => {
|
||||
const colors = {
|
||||
bird: ['#36CFFF', '#2861F5'],
|
||||
mammal: ['#FF36D9', '#C92AF5'],
|
||||
fish: ['#FFB72C', '#F5612A'],
|
||||
amphibian: ['#4EF568', '#2AB256'],
|
||||
reptile: ['#36FFB0', '#2AF5A1'],
|
||||
insect: ['#7636FF', '#2A3CF5'],
|
||||
plant: ['#FF7636', '#F52A2A']
|
||||
}
|
||||
return colors[category] || ['#36CFFF', '#2861F5']
|
||||
}
|
||||
|
||||
// 获取巡护统计数据
|
||||
const fetchPatrolData = async () => {
|
||||
try {
|
||||
const res = await request.get('/api/admin/patrol/records/statistics/overview')
|
||||
if (res.success && res.data) {
|
||||
updatePatrolChart(res.data)
|
||||
const { overview } = res.data
|
||||
const chartData = [
|
||||
{
|
||||
name: '已完成',
|
||||
value: Number(overview.completed_count) || 0,
|
||||
itemStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: '#67C23A' },
|
||||
{ offset: 1, color: '#67C23A99' }
|
||||
])
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '进行中',
|
||||
value: Number(overview.in_progress_count) || 0,
|
||||
itemStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: '#409EFF' },
|
||||
{ offset: 1, color: '#409EFF99' }
|
||||
])
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '总任务',
|
||||
value: Number(overview.total_count) || 0,
|
||||
itemStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: '#909399' },
|
||||
{ offset: 1, color: '#90939999' }
|
||||
])
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
patrolChart.setOption({
|
||||
xAxis: {
|
||||
data: chartData.map(item => item.name)
|
||||
},
|
||||
series: [{
|
||||
data: chartData,
|
||||
label: {
|
||||
show: true,
|
||||
position: 'top',
|
||||
color: '#fff',
|
||||
formatter: function(params) {
|
||||
return Math.floor(params.value)
|
||||
}
|
||||
}
|
||||
}]
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取巡护统计数据失败:', error)
|
||||
@ -39,54 +129,50 @@ const initSpeciesChart = () => {
|
||||
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}%)'
|
||||
formatter: function(params) {
|
||||
const percent = parseInt(params.percent)
|
||||
return `${params.name}: ${parseInt(params.value)}种 (${percent}%)`
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
orient: 'vertical',
|
||||
right: '5%',
|
||||
top: 'middle',
|
||||
textStyle: {
|
||||
color: '#fff'
|
||||
}
|
||||
show: false
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '物种分布',
|
||||
type: 'pie',
|
||||
radius: ['40%', '70%'],
|
||||
center: ['40%', '55%'],
|
||||
center: ['50%', '50%'],
|
||||
avoidLabelOverlap: true,
|
||||
itemStyle: {
|
||||
borderRadius: 10,
|
||||
borderColor: '#fff',
|
||||
borderColor: 'rgba(0, 0, 0, 0.2)',
|
||||
borderWidth: 2
|
||||
},
|
||||
label: {
|
||||
show: true,
|
||||
position: 'outside',
|
||||
formatter: function(params) {
|
||||
return `${params.name}\n${parseInt(params.value)}种`
|
||||
},
|
||||
color: '#fff',
|
||||
formatter: '{b}\n{c}种'
|
||||
fontSize: 12
|
||||
},
|
||||
emphasis: {
|
||||
label: {
|
||||
show: true,
|
||||
fontSize: 16,
|
||||
fontWeight: 'bold'
|
||||
labelLine: {
|
||||
length: 15,
|
||||
length2: 0,
|
||||
maxSurfaceAngle: 80,
|
||||
lineStyle: {
|
||||
color: 'rgba(255, 255, 255, 0.3)'
|
||||
}
|
||||
},
|
||||
data: []
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
speciesChart.setOption(option)
|
||||
}
|
||||
|
||||
@ -97,19 +183,13 @@ const initPatrolChart = () => {
|
||||
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'
|
||||
},
|
||||
formatter: function(params) {
|
||||
return `${params[0].name}: ${Math.floor(params[0].value)}`
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
@ -121,7 +201,7 @@ const initPatrolChart = () => {
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: ['已完成', '进行中', '未开始', '已超时'],
|
||||
data: ['已完成', '进行中', '总任务'],
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: 'rgba(255, 255, 255, 0.3)'
|
||||
@ -134,6 +214,9 @@ const initPatrolChart = () => {
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
minInterval: 1,
|
||||
splitNumber: 4,
|
||||
min: 0,
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
color: 'rgba(255, 255, 255, 0.1)',
|
||||
@ -141,7 +224,10 @@ const initPatrolChart = () => {
|
||||
}
|
||||
},
|
||||
axisLabel: {
|
||||
color: '#fff'
|
||||
color: '#fff',
|
||||
formatter: function(value) {
|
||||
return Math.floor(value)
|
||||
}
|
||||
}
|
||||
},
|
||||
series: [
|
||||
@ -154,7 +240,10 @@ const initPatrolChart = () => {
|
||||
label: {
|
||||
show: true,
|
||||
position: 'top',
|
||||
color: '#fff'
|
||||
color: '#fff',
|
||||
formatter: function(params) {
|
||||
return parseInt(params.value || 0) + '个'
|
||||
}
|
||||
},
|
||||
data: []
|
||||
}
|
||||
@ -163,62 +252,6 @@ const initPatrolChart = () => {
|
||||
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 = () => {
|
||||
@ -295,10 +328,10 @@ onUnmounted(() => {
|
||||
border-radius: 4px;
|
||||
padding: 12px;
|
||||
|
||||
.chart-title {
|
||||
font-size: 16px;
|
||||
.chart-title {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
margin-bottom: 12px;
|
||||
background: linear-gradient(to bottom, #ffffff, #3fa7dd);
|
||||
|
@ -1,17 +1,11 @@
|
||||
<script setup>
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
import * as echarts from 'echarts'
|
||||
import { getMonitoringData, getLatestData, getStatistics, getQualityStatistics } from '@/api/monitoring'
|
||||
import { getLatestData } from '@/api/monitoring'
|
||||
|
||||
// 图表实例
|
||||
let trendChart = null
|
||||
let qualityChart = null
|
||||
let indicatorChart = null
|
||||
|
||||
// 图表容器引用
|
||||
const trendChartRef = ref(null)
|
||||
const qualityChartRef = ref(null)
|
||||
const indicatorChartRef = ref(null)
|
||||
let chart = null
|
||||
const chartRef = ref(null)
|
||||
|
||||
// 指标选项
|
||||
const indicatorOptions = [
|
||||
@ -21,72 +15,111 @@ const indicatorOptions = [
|
||||
{ label: '浊度', unit: 'NTU', threshold: { min: 0, max: 10 } }
|
||||
]
|
||||
|
||||
// 更新实时指标图表
|
||||
const updateIndicatorChart = async () => {
|
||||
if (!indicatorChart || !indicatorChartRef.value) return
|
||||
// 初始化图表
|
||||
const initChart = () => {
|
||||
if (!chartRef.value) return
|
||||
|
||||
chart = echarts.init(chartRef.value)
|
||||
chart.setOption({
|
||||
backgroundColor: 'transparent',
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow'
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
top: '10%',
|
||||
right: '5%',
|
||||
bottom: '10%',
|
||||
left: '10%',
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: [],
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: 'rgba(255, 255, 255, 0.3)'
|
||||
}
|
||||
},
|
||||
axisLabel: {
|
||||
color: 'rgba(255, 255, 255, 0.7)',
|
||||
interval: 0
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
color: 'rgba(255, 255, 255, 0.1)',
|
||||
type: 'dashed'
|
||||
}
|
||||
},
|
||||
axisLine: {
|
||||
show: false
|
||||
},
|
||||
axisTick: {
|
||||
show: false
|
||||
},
|
||||
axisLabel: {
|
||||
color: 'rgba(255, 255, 255, 0.7)'
|
||||
}
|
||||
},
|
||||
series: [
|
||||
{
|
||||
type: 'bar',
|
||||
barWidth: '40%',
|
||||
itemStyle: {
|
||||
borderRadius: [4, 4, 0, 0]
|
||||
},
|
||||
label: {
|
||||
show: true,
|
||||
position: 'top',
|
||||
color: '#fff'
|
||||
},
|
||||
data: []
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
// 更新图表数据
|
||||
const updateChart = async () => {
|
||||
if (!chart || !chartRef.value) return
|
||||
|
||||
try {
|
||||
const res = await getLatestData({
|
||||
point_id: 1,
|
||||
indicator_ids: [1, 2, 3, 4],
|
||||
device_id: 1
|
||||
point_id: '1',
|
||||
indicator_ids: '1,2,3,4',
|
||||
device_id: '1'
|
||||
})
|
||||
|
||||
if (res.success && res.data) {
|
||||
const data = res.data.map(item => {
|
||||
const indicator = indicatorOptions[item.indicator_id - 1]
|
||||
const indicator = indicatorOptions[Number(item.indicator_id) - 1]
|
||||
const value = Number(item.value)
|
||||
const isWarning = value < indicator.threshold.min || value > indicator.threshold.max
|
||||
return {
|
||||
name: indicator.label,
|
||||
value: value,
|
||||
itemStyle: {
|
||||
color: isWarning ? '#E6A23C' : '#67C23A'
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: isWarning ? '#E6A23C' : '#67C23A' },
|
||||
{ offset: 1, color: isWarning ? '#F56C6C' : '#95D475' }
|
||||
])
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
indicatorChart.setOption({
|
||||
title: {
|
||||
text: '实时监测指标',
|
||||
textStyle: {
|
||||
color: '#fff',
|
||||
fontSize: 14
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow'
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '3%',
|
||||
containLabel: true
|
||||
},
|
||||
chart.setOption({
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: data.map(item => item.name),
|
||||
axisLine: { lineStyle: { color: '#fff' } },
|
||||
axisLabel: { color: '#fff' }
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
axisLine: { lineStyle: { color: '#fff' } },
|
||||
axisLabel: { color: '#fff' },
|
||||
splitLine: { lineStyle: { color: 'rgba(255,255,255,0.1)' } }
|
||||
data: data.map(item => item.name)
|
||||
},
|
||||
series: [
|
||||
{
|
||||
type: 'bar',
|
||||
data: data,
|
||||
barWidth: '40%',
|
||||
label: {
|
||||
show: true,
|
||||
position: 'top',
|
||||
color: '#fff',
|
||||
formatter: (params) => {
|
||||
const indicator = indicatorOptions[params.dataIndex]
|
||||
return params.value + (indicator.unit || '')
|
||||
@ -101,204 +134,43 @@ const updateIndicatorChart = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 更新趋势图表
|
||||
const updateTrendChart = async () => {
|
||||
if (!trendChart || !trendChartRef.value) return
|
||||
|
||||
try {
|
||||
const now = new Date()
|
||||
let startDate = new Date()
|
||||
startDate.setHours(startDate.getHours() - 24)
|
||||
|
||||
const res = await getMonitoringData({
|
||||
start_date: startDate.toISOString(),
|
||||
end_date: now.toISOString(),
|
||||
indicator_ids: [1], // 默认显示水温趋势
|
||||
point_id: 1,
|
||||
device_id: 1
|
||||
})
|
||||
|
||||
if (res.success && res.data) {
|
||||
const data = res.data.map(item => [
|
||||
new Date(item.timestamp).getTime(),
|
||||
item.value
|
||||
])
|
||||
|
||||
trendChart.setOption({
|
||||
title: {
|
||||
text: '24小时水温趋势',
|
||||
textStyle: {
|
||||
color: '#fff',
|
||||
fontSize: 14
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis'
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '3%',
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: {
|
||||
type: 'time',
|
||||
axisLine: { lineStyle: { color: '#fff' } },
|
||||
axisLabel: { color: '#fff' }
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
name: '°C',
|
||||
nameTextStyle: { color: '#fff' },
|
||||
axisLine: { lineStyle: { color: '#fff' } },
|
||||
axisLabel: { color: '#fff' },
|
||||
splitLine: { lineStyle: { color: 'rgba(255,255,255,0.1)' } }
|
||||
},
|
||||
series: [{
|
||||
name: '水温',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
symbol: 'none',
|
||||
areaStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: 'rgba(58,77,233,0.8)' },
|
||||
{ offset: 1, color: 'rgba(58,77,233,0.1)' }
|
||||
])
|
||||
},
|
||||
data: data
|
||||
}]
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取趋势数据失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 更新数据质量图表
|
||||
const updateQualityChart = async () => {
|
||||
if (!qualityChart || !qualityChartRef.value) return
|
||||
|
||||
try {
|
||||
const now = new Date()
|
||||
let startDate = new Date()
|
||||
startDate.setDate(startDate.getDate() - 30)
|
||||
|
||||
const res = await getQualityStatistics({
|
||||
start_date: startDate.toISOString(),
|
||||
end_date: now.toISOString(),
|
||||
point_id: 1
|
||||
})
|
||||
|
||||
if (res.success && res.data) {
|
||||
qualityChart.setOption({
|
||||
title: {
|
||||
text: '数据质量分析',
|
||||
textStyle: {
|
||||
color: '#fff',
|
||||
fontSize: 14
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'item'
|
||||
},
|
||||
legend: {
|
||||
orient: 'vertical',
|
||||
right: 10,
|
||||
top: 'center',
|
||||
textStyle: { color: '#fff' }
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '数据质量',
|
||||
type: 'pie',
|
||||
radius: ['40%', '70%'],
|
||||
avoidLabelOverlap: false,
|
||||
itemStyle: {
|
||||
borderRadius: 10,
|
||||
borderColor: '#fff',
|
||||
borderWidth: 2
|
||||
},
|
||||
label: {
|
||||
show: true,
|
||||
position: 'inside',
|
||||
formatter: '{d}%',
|
||||
fontSize: 12,
|
||||
color: '#fff'
|
||||
},
|
||||
data: [
|
||||
{ value: res.data.valid_rate, name: '有效数据', itemStyle: { color: '#67C23A' } },
|
||||
{ value: res.data.invalid_rate, name: '无效数据', itemStyle: { color: '#F56C6C' } },
|
||||
{ value: res.data.missing_rate, name: '缺失数据', itemStyle: { color: '#E6A23C' } }
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取数据质量统计失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化图表
|
||||
const initCharts = () => {
|
||||
if (indicatorChartRef.value) {
|
||||
indicatorChart = echarts.init(indicatorChartRef.value)
|
||||
}
|
||||
if (trendChartRef.value) {
|
||||
trendChart = echarts.init(trendChartRef.value)
|
||||
}
|
||||
if (qualityChartRef.value) {
|
||||
qualityChart = echarts.init(qualityChartRef.value)
|
||||
}
|
||||
// 定时更新数据
|
||||
let timer = null
|
||||
const startAutoRefresh = () => {
|
||||
updateChart()
|
||||
timer = setInterval(updateChart, 60000) // 每分钟更新一次
|
||||
}
|
||||
|
||||
// 处理窗口大小变化
|
||||
const handleResize = () => {
|
||||
indicatorChart?.resize()
|
||||
trendChart?.resize()
|
||||
qualityChart?.resize()
|
||||
}
|
||||
|
||||
// 定时更新数据
|
||||
let updateTimer = null
|
||||
const startDataUpdate = () => {
|
||||
updateIndicatorChart()
|
||||
updateTimer = setInterval(() => {
|
||||
updateIndicatorChart()
|
||||
}, 60000) // 每分钟更新一次
|
||||
chart?.resize()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
initCharts()
|
||||
startDataUpdate()
|
||||
updateTrendChart()
|
||||
updateQualityChart()
|
||||
initChart()
|
||||
startAutoRefresh()
|
||||
window.addEventListener('resize', handleResize)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (updateTimer) {
|
||||
clearInterval(updateTimer)
|
||||
if (timer) {
|
||||
clearInterval(timer)
|
||||
}
|
||||
window.removeEventListener('resize', handleResize)
|
||||
indicatorChart?.dispose()
|
||||
trendChart?.dispose()
|
||||
qualityChart?.dispose()
|
||||
chart?.dispose()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="middle-card">
|
||||
<div class="card-title">监测数据</div>
|
||||
<div class="card-header">
|
||||
<div class="title">监测数据</div>
|
||||
<div class="update-time">实时监测中</div>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<!-- 实时指标图表 -->
|
||||
<div ref="indicatorChartRef" class="indicator-chart"></div>
|
||||
|
||||
<!-- 趋势图表 -->
|
||||
<div ref="trendChartRef" class="trend-chart"></div>
|
||||
|
||||
<!-- 数据质量图表 -->
|
||||
<div ref="qualityChartRef" class="quality-chart"></div>
|
||||
<div class="chart-item">
|
||||
<div ref="chartRef" class="chart"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -307,36 +179,67 @@ onUnmounted(() => {
|
||||
.middle-card {
|
||||
height: 100%;
|
||||
padding: 16px;
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
border-radius: 8px;
|
||||
box-sizing: border-box;
|
||||
background: rgba(6, 30, 93, 0.5);
|
||||
border-radius: 4px;
|
||||
|
||||
.card-title {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #fff;
|
||||
margin-bottom: 16px;
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
|
||||
.title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
background: linear-gradient(to bottom, #ffffff, #3fa7dd);
|
||||
-webkit-background-clip: text;
|
||||
color: transparent;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
|
||||
.update-time {
|
||||
font-size: 14px;
|
||||
color: #3fa7dd;
|
||||
opacity: 0.8;
|
||||
position: relative;
|
||||
padding-left: 20px;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background: #67C23A;
|
||||
border-radius: 50%;
|
||||
animation: blink 1s infinite;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card-content {
|
||||
height: calc(100% - 32px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
height: calc(100% - 60px);
|
||||
|
||||
.indicator-chart {
|
||||
flex: 1;
|
||||
min-height: 180px;
|
||||
}
|
||||
.chart-item {
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
border-radius: 4px;
|
||||
padding: 12px;
|
||||
|
||||
.trend-chart {
|
||||
flex: 2;
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
.quality-chart {
|
||||
flex: 1;
|
||||
min-height: 180px;
|
||||
.chart {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
0% { opacity: 0.2; }
|
||||
50% { opacity: 1; }
|
||||
100% { opacity: 0.2; }
|
||||
}
|
||||
</style>
|
Loading…
x
Reference in New Issue
Block a user