This commit is contained in:
Xiaoyu 2025-03-13 10:14:00 +08:00
commit 0d5d1e6072
5 changed files with 196 additions and 200 deletions

View File

@ -397,7 +397,7 @@ const initTrendChart = () => {
xAxis: { xAxis: {
type: "category", type: "category",
boundaryGap: false, boundaryGap: false,
data: ["3-14", "3-15", "3-16", "3-17", "3-18", "3-19", "3-20"], data: ["2-14", "2-15", "2-16", "2-17", "2-18", "2-19", "2-20"],
axisLine: { axisLine: {
lineStyle: { lineStyle: {
color: "#DCDFE6", color: "#DCDFE6",
@ -719,66 +719,6 @@ onUnmounted(() => {
<div ref="protectionChartRef" class="chart-content"></div> <div ref="protectionChartRef" class="chart-content"></div>
</div> </div>
</div> </div>
<div class="bottom-stats-grid">
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">实时监测数据</span>
</div>
<div class="stat-content">
<div class="stat-item">
<span class="label">水质指标</span>
<span class="value good"></span>
</div>
<div class="stat-item">
<span class="label">空气质量</span>
<span class="value normal"></span>
</div>
<div class="stat-item">
<span class="label">土壤湿度</span>
<span class="value">42%</span>
</div>
</div>
</div>
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">今日巡护概况</span>
</div>
<div class="stat-content">
<div class="stat-item">
<span class="label">巡护人员</span>
<span class="value">8</span>
</div>
<div class="stat-item">
<span class="label">巡护里程</span>
<span class="value">12.5km</span>
</div>
<div class="stat-item">
<span class="label">记录上报</span>
<span class="value">26</span>
</div>
</div>
</div>
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">物种活动</span>
</div>
<div class="stat-content">
<div class="stat-item">
<span class="label">活动区域</span>
<span class="value">A3B5区</span>
</div>
<div class="stat-item">
<span class="label">活跃物种</span>
<span class="value">15</span>
</div>
<div class="stat-item">
<span class="label">监测频次</span>
<span class="value">4/</span>
</div>
</div>
</div>
</div>
</div> </div>
</template> </template>
@ -959,64 +899,5 @@ onUnmounted(() => {
} }
} }
} }
.bottom-stats-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20px;
margin-top: 40px;
padding-bottom: 20px;
.stat-card {
background: #fff;
border-radius: 8px;
padding: 16px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
.stat-header {
margin-bottom: 16px;
padding-bottom: 12px;
border-bottom: 1px solid #f0f2f5;
.stat-title {
font-size: 16px;
font-weight: 500;
color: #303133;
}
}
.stat-content {
.stat-item {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
&:last-child {
margin-bottom: 0;
}
.label {
color: #909399;
font-size: 14px;
}
.value {
font-size: 14px;
font-weight: 500;
color: #303133;
&.good {
color: #67c23a;
}
&.normal {
color: #e6a23c;
}
}
}
}
}
}
} }
</style> </style>

View File

@ -92,37 +92,25 @@ const initChart = async () => {
const option = { const option = {
backgroundColor: 'transparent', backgroundColor: 'transparent',
tooltip: { tooltip: {
trigger: 'item' trigger: 'item',
}, formatter: '{b}: {c}'
legend: {
orient: 'vertical',
left: '0%',
top: 'middle',
textStyle: {
color: '#fff',
fontSize: 14
},
itemGap: 20,
icon: 'circle',
itemWidth: 10,
itemHeight: 10,
formatter: function(name) {
return name
}
}, },
radar: { radar: {
center: ['65%', '50%'], center: ['50%', '50%'],
radius: '60%', radius: '65%',
indicator: [], indicator: [],
shape: 'circle', shape: 'circle',
splitNumber: 5, splitNumber: 8,
axisName: { axisName: {
color: 'rgba(255, 255, 255, 0.7)', color: 'rgba(255, 255, 255, 0.7)',
fontSize: 12 fontSize: 12
}, },
splitLine: { splitLine: {
lineStyle: { lineStyle: {
color: 'rgba(255, 255, 255, 0.1)' color: 'rgba(255, 255, 255, 0.2)',
width: 2,
type: 'dashed',
dashOffset: 0
} }
}, },
splitArea: { splitArea: {
@ -132,8 +120,9 @@ const initChart = async () => {
} }
}, },
axisLine: { axisLine: {
show: true,
lineStyle: { lineStyle: {
color: 'rgba(255, 255, 255, 0.1)' color: 'rgba(255, 255, 255, 0.3)'
} }
} }
}, },
@ -142,6 +131,27 @@ const initChart = async () => {
chart.setOption(option) chart.setOption(option)
//
let angle = 0
let dashOffset = 0
const animate = () => {
angle = (angle + 0.5) % 360
dashOffset = (dashOffset + 1) % 20
chart.setOption({
radar: {
startAngle: angle,
splitLine: {
lineStyle: {
dashOffset: -dashOffset
}
}
}
})
requestAnimationFrame(animate)
}
animate()
// //
setTimeout(() => { setTimeout(() => {
if (chart) { if (chart) {
@ -183,14 +193,18 @@ const updateChart = () => {
// //
const series = [{ const series = [{
type: 'radar', type: 'radar',
animation: true,
animationDuration: 2000,
animationEasing: 'quadraticInOut',
data: points.map(point => { data: points.map(point => {
const pointType = point.type const pointType = point.type
const colors = { const colors = {
water: ['#409EFF', '#36CE9E'], water: ['rgba(64, 158, 255, 1)', 'rgba(54, 206, 158, 0.1)'],
air: ['#E6A23C', '#F56C6C'], air: ['rgba(230, 162, 60, 1)', 'rgba(245, 108, 108, 0.1)'],
soil: ['#67C23A', '#95D475'] soil: ['rgba(103, 194, 58, 1)', 'rgba(149, 212, 117, 0.1)'],
default: ['rgba(64, 158, 255, 1)', 'rgba(54, 206, 158, 0.1)']
} }
const colorSet = colors[pointType] || colors.water const colorSet = colors[pointType] || colors.default
return { return {
name: point.point, name: point.point,
@ -198,27 +212,36 @@ const updateChart = () => {
const ind = point.indicators.find(i => i.name === indicator.name) const ind = point.indicators.find(i => i.name === indicator.name)
return ind ? ind.value : 0 return ind ? ind.value : 0
}), }),
symbol: 'circle',
symbolSize: 6,
itemStyle: { itemStyle: {
color: colorSet[0] color: colorSet[0],
borderColor: '#fff',
borderWidth: 2,
shadowColor: colorSet[0],
shadowBlur: 10
},
lineStyle: {
color: colorSet[0],
width: 2,
type: [5, 10],
shadowColor: colorSet[0],
shadowBlur: 5
}, },
areaStyle: { areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: colorSet[0] }, { offset: 0, color: colorSet[0] },
{ offset: 1, color: colorSet[1] } { offset: 1, color: colorSet[1] }
]), ]),
opacity: 0.3 opacity: 0.5
}, },
lineStyle: {
width: 2
},
symbol: 'circle',
symbolSize: 6,
emphasis: { emphasis: {
lineStyle: { scale: true,
width: 4 itemStyle: {
shadowBlur: 20
}, },
areaStyle: { areaStyle: {
opacity: 0.5 opacity: 0.8
} }
} }
} }
@ -226,14 +249,34 @@ const updateChart = () => {
}] }]
chart.setOption({ chart.setOption({
legend: {
data: points.map(item => item.point)
},
radar: { radar: {
indicator: radarIndicators indicator: radarIndicators
}, },
series: series series: series
}) })
//
let breatheEffect = 0
const animate = () => {
breatheEffect = (breatheEffect + 1) % 100
const opacity = 0.3 + Math.sin(breatheEffect * Math.PI / 50) * 0.2
series[0].data.forEach((item, index) => {
chart.setOption({
series: [{
data: series[0].data.map((dataItem, i) => ({
...dataItem,
areaStyle: {
...dataItem.areaStyle,
opacity: i === index ? opacity : 0.3
}
}))
}]
})
})
requestAnimationFrame(animate)
}
animate()
} }
// //
@ -369,6 +412,7 @@ onUnmounted(() => {
background: rgba(0, 0, 0, 0.2); background: rgba(0, 0, 0, 0.2);
border-radius: 4px; border-radius: 4px;
padding: 12px; padding: 12px;
position: relative;
.chart { .chart {
height: 100%; height: 100%;

View File

@ -9,6 +9,14 @@ const chartRef = ref(null)
// //
const alertData = ref({ const alertData = ref({
total: 0, total: 0,
level1: {
name: '低微预警',
total: 0,
color: '#909399',
pending: 0,
processed: 0,
ignored: 0
},
level2: { level2: {
name: '中等预警', name: '中等预警',
total: 0, total: 0,
@ -75,7 +83,7 @@ const initChart = async () => {
}, },
xAxis: { xAxis: {
type: 'category', type: 'category',
data: ['严重预警', '中等预警'], data: ['严重预警', '中等预警', '低微预警'],
axisLine: { axisLine: {
lineStyle: { lineStyle: {
color: 'rgba(255, 255, 255, 0.3)' color: 'rgba(255, 255, 255, 0.3)'
@ -135,7 +143,7 @@ const initChart = async () => {
]) ])
} }
}, },
data: [0, 0] data: [0, 0, 0]
}, },
{ {
name: '已处理', name: '已处理',
@ -156,7 +164,7 @@ const initChart = async () => {
]) ])
} }
}, },
data: [0, 0] data: [0, 0, 0]
}, },
{ {
name: '已忽略', name: '已忽略',
@ -177,7 +185,7 @@ const initChart = async () => {
]) ])
} }
}, },
data: [0, 0] data: [0, 0, 0]
} }
] ]
} }
@ -192,6 +200,7 @@ const fetchAlertData = async () => {
if (res.success && res.data) { if (res.success && res.data) {
// //
let total = 0 let total = 0
let level1Data = { pending: 0, processed: 0, ignored: 0 }
let level2Data = { pending: 0, processed: 0, ignored: 0 } let level2Data = { pending: 0, processed: 0, ignored: 0 }
let level3Data = { pending: 0, processed: 0, ignored: 0 } let level3Data = { pending: 0, processed: 0, ignored: 0 }
@ -199,7 +208,13 @@ const fetchAlertData = async () => {
const count = Number(item.total_count) || 0 const count = Number(item.total_count) || 0
total += count total += count
if (item.alert_level === 2) { if (item.alert_level === 1) {
level1Data = {
pending: Number(item.pending_count) || 0,
processed: Number(item.processed_count) || 0,
ignored: Number(item.ignored_count) || 0
}
} else if (item.alert_level === 2) {
level2Data = { level2Data = {
pending: Number(item.pending_count) || 0, pending: Number(item.pending_count) || 0,
processed: Number(item.processed_count) || 0, processed: Number(item.processed_count) || 0,
@ -221,15 +236,15 @@ const fetchAlertData = async () => {
series: [ series: [
{ {
name: '待处理', name: '待处理',
data: [level3Data.pending, level2Data.pending] data: [level3Data.pending, level2Data.pending, level1Data.pending]
}, },
{ {
name: '已处理', name: '已处理',
data: [level3Data.processed, level2Data.processed] data: [level3Data.processed, level2Data.processed, level1Data.processed]
}, },
{ {
name: '已忽略', name: '已忽略',
data: [level3Data.ignored, level2Data.ignored] data: [level3Data.ignored, level2Data.ignored, level1Data.ignored]
} }
] ]
}) })

View File

@ -34,9 +34,9 @@ const initChart = async () => {
} }
}, },
grid: { grid: {
top: '3%', top: '8%',
right: '5%', right: '15%',
bottom: '3%', bottom: '15%',
left: '15%', left: '15%',
containLabel: true containLabel: true
}, },
@ -46,7 +46,7 @@ const initChart = async () => {
nameTextStyle: { nameTextStyle: {
color: 'rgba(255, 255, 255, 0.7)', color: 'rgba(255, 255, 255, 0.7)',
fontSize: 12, fontSize: 12,
padding: [0, 0, 0, 20] padding: [15, 0, 0, 20]
}, },
axisLine: { axisLine: {
show: false show: false
@ -62,7 +62,9 @@ const initChart = async () => {
}, },
axisLabel: { axisLabel: {
color: 'rgba(255, 255, 255, 0.7)', color: 'rgba(255, 255, 255, 0.7)',
fontSize: 12 fontSize: 12,
margin: 12,
padding: [8, 0, 0, 0]
} }
}, },
yAxis: { yAxis: {
@ -78,15 +80,21 @@ const initChart = async () => {
}, },
axisLabel: { axisLabel: {
color: '#fff', color: '#fff',
fontSize: 12, fontSize: 14,
margin: 16 margin: 20,
formatter: function (value) {
if (value.length > 6) {
return value.substring(0, 6) + '...'
}
return value
}
} }
}, },
series: [ series: [
{ {
name: '预警次数', name: '预警次数',
type: 'bar', type: 'bar',
barWidth: 16, barWidth: 12,
showBackground: true, showBackground: true,
backgroundStyle: { backgroundStyle: {
color: 'rgba(255, 255, 255, 0.05)', color: 'rgba(255, 255, 255, 0.05)',
@ -98,8 +106,9 @@ const initChart = async () => {
label: { label: {
show: true, show: true,
position: 'right', position: 'right',
distance: 15,
color: '#fff', color: '#fff',
fontSize: 12, fontSize: 14,
formatter: '{c}次' formatter: '{c}次'
}, },
data: [] data: []
@ -115,7 +124,7 @@ const fetchIndicatorData = async () => {
try { try {
const res = await getAlertStatisticsByIndicator() const res = await getAlertStatisticsByIndicator()
if (res.success && res.data) { if (res.success && res.data) {
// // 6
const chartData = res.data const chartData = res.data
.filter(item => item.indicator_name) .filter(item => item.indicator_name)
.map(item => ({ .map(item => ({
@ -128,20 +137,45 @@ const fetchIndicatorData = async () => {
} }
})) }))
.sort((a, b) => b.value - a.value) .sort((a, b) => b.value - a.value)
.slice(0, 6) // 6
// //
chart.setOption({ chart.setOption({
grid: {
top: '8%',
right: '20%',
bottom: '8%',
left: '0%',
containLabel: true
},
yAxis: { yAxis: {
data: chartData.map(item => item.name) data: chartData.map(item => item.name),
axisLabel: {
margin: 20,
fontSize: 14
}
}, },
series: [ series: [
{ {
data: chartData, data: chartData,
barWidth: 12,
itemStyle: {
borderRadius: [0, 4, 4, 0]
},
label: {
show: true,
position: 'right',
distance: 15,
color: '#fff',
fontSize: 14,
formatter: '{c}次'
},
markLine: { markLine: {
silent: true, silent: true,
symbol: ['none', 'none'], symbol: ['none', 'none'],
lineStyle: { lineStyle: {
color: 'rgba(255, 255, 255, 0.3)' color: 'rgba(255, 255, 255, 0.2)',
type: [5, 10]
}, },
data: [ data: [
{ {
@ -153,55 +187,77 @@ const fetchIndicatorData = async () => {
} }
} }
] ]
},
animationDelay: function(idx) {
return idx * 100
} }
} }
] ]
}) })
//
let currentIndex = 0
const animate = () => {
if (currentIndex >= chartData.length) {
currentIndex = 0
}
chart.dispatchAction({
type: 'highlight',
seriesIndex: 0,
dataIndex: currentIndex
})
//
const lastIndex = (currentIndex - 1 + chartData.length) % chartData.length
chart.dispatchAction({
type: 'downplay',
seriesIndex: 0,
dataIndex: lastIndex
})
currentIndex++
setTimeout(animate, 2000)
}
animate()
} }
} catch (error) { } catch (error) {
console.error('获取指标预警统计数据失败:', error) console.error('获取指标预警统计数据失败:', error)
} }
} }
// //
const getIndicatorColor = (indicatorName) => { const getIndicatorColor = (indicatorName) => {
const colors = { const colors = {
'水体pH值': new echarts.graphic.LinearGradient(1, 0, 0, 0, [ '水体pH值': new echarts.graphic.LinearGradient(1, 0, 0, 0, [
{ offset: 0, color: '#36CFFF' }, { offset: 0, color: 'rgba(54, 207, 255, 1)' },
{ offset: 1, color: '#2861F5' } { offset: 1, color: 'rgba(40, 97, 245, 0.8)' }
]), ]),
'溶解氧': new echarts.graphic.LinearGradient(1, 0, 0, 0, [ '溶解氧': new echarts.graphic.LinearGradient(1, 0, 0, 0, [
{ offset: 0, color: '#4EF568' }, { offset: 0, color: 'rgba(78, 245, 104, 1)' },
{ offset: 1, color: '#2AB256' } { offset: 1, color: 'rgba(42, 178, 86, 0.8)' }
]), ]),
'盐度': new echarts.graphic.LinearGradient(1, 0, 0, 0, [ '盐度': new echarts.graphic.LinearGradient(1, 0, 0, 0, [
{ offset: 0, color: '#FFB72C' }, { offset: 0, color: 'rgba(255, 183, 44, 1)' },
{ offset: 1, color: '#F5612A' } { offset: 1, color: 'rgba(245, 97, 42, 0.8)' }
]), ]),
'水温': new echarts.graphic.LinearGradient(1, 0, 0, 0, [ '水温': new echarts.graphic.LinearGradient(1, 0, 0, 0, [
{ offset: 0, color: '#FF36D9' }, { offset: 0, color: 'rgba(255, 54, 217, 1)' },
{ offset: 1, color: '#C92AF5' } { offset: 1, color: 'rgba(201, 42, 245, 0.8)' }
]), ]),
'浊度': new echarts.graphic.LinearGradient(1, 0, 0, 0, [ '浊度': new echarts.graphic.LinearGradient(1, 0, 0, 0, [
{ offset: 0, color: '#36FFB0' }, { offset: 0, color: 'rgba(54, 255, 176, 1)' },
{ offset: 1, color: '#2AF5A1' } { offset: 1, color: 'rgba(42, 245, 161, 0.8)' }
]), ]),
'PM2.5': new echarts.graphic.LinearGradient(1, 0, 0, 0, [ 'PM2.5': new echarts.graphic.LinearGradient(1, 0, 0, 0, [
{ offset: 0, color: '#7636FF' }, { offset: 0, color: 'rgba(118, 54, 255, 1)' },
{ offset: 1, color: '#2A3CF5' } { offset: 1, color: 'rgba(42, 60, 245, 0.8)' }
]), ]),
'氮氧化物': new echarts.graphic.LinearGradient(1, 0, 0, 0, [ '氮氧化物': new echarts.graphic.LinearGradient(1, 0, 0, 0, [
{ offset: 0, color: '#FF7636' }, { offset: 0, color: 'rgba(255, 118, 54, 1)' },
{ offset: 1, color: '#F52A2A' } { offset: 1, color: 'rgba(245, 42, 42, 0.8)' }
]) ])
} }
return colors[indicatorName] || new echarts.graphic.LinearGradient(1, 0, 0, 0, [ return colors[indicatorName] || new echarts.graphic.LinearGradient(1, 0, 0, 0, [
{ offset: 0, color: '#36CFFF' }, { offset: 0, color: 'rgba(54, 207, 255, 1)' },
{ offset: 1, color: '#2861F5' } { offset: 1, color: 'rgba(40, 97, 245, 0.8)' }
]) ])
} }

View File

@ -40,7 +40,7 @@ const initChart = async () => {
fontSize: 12 fontSize: 12
}, },
top: 0, top: 0,
right: '5%', right: '0%',
itemWidth: 12, itemWidth: 12,
itemHeight: 12 itemHeight: 12
}, },