修复大屏的一些BUG
This commit is contained in:
parent
e661974160
commit
f081d836af
@ -8,23 +8,7 @@ import { getDeviceList, deleteDevice } from "@/api/device";
|
|||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
|
||||||
// 传感器列表数据
|
// 传感器列表数据
|
||||||
const sensorList = ref([
|
const sensorList = ref([]);
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
device_name: "RK500-13-水质传感器-01",
|
|
||||||
device_code: "RK500-001",
|
|
||||||
device_type: 0,
|
|
||||||
status: 0,
|
|
||||||
install_location: "湿地区域A",
|
|
||||||
updated_at: new Date().toISOString(),
|
|
||||||
data: {
|
|
||||||
temp: null,
|
|
||||||
ph: null,
|
|
||||||
conductivity: null,
|
|
||||||
turbidity: null,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
// 分页参数
|
// 分页参数
|
||||||
const pagination = ref({
|
const pagination = ref({
|
||||||
@ -34,41 +18,41 @@ const pagination = ref({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 获取传感器列表
|
// 获取传感器列表
|
||||||
// const getSensorList = async () => {
|
const getSensorList = async () => {
|
||||||
// loading.value = true;
|
loading.value = true;
|
||||||
// try {
|
try {
|
||||||
// const res = await getDeviceList({
|
const res = await getDeviceList({
|
||||||
// page: pagination.value.page,
|
page: pagination.value.page,
|
||||||
// page_size: pagination.value.page_size,
|
page_size: pagination.value.page_size,
|
||||||
// device_type: 0,
|
device_type: 0, // 传感器类型为0
|
||||||
// });
|
});
|
||||||
// if (res.success) {
|
if (res.success) {
|
||||||
// sensorList.value = res.data.list || [];
|
sensorList.value = res.data.list || [];
|
||||||
// if (res.data.pagination) {
|
if (res.data.pagination) {
|
||||||
// pagination.value.total = res.data.pagination.total;
|
pagination.value.total = res.data.pagination.total;
|
||||||
// }
|
}
|
||||||
// } else {
|
} else {
|
||||||
// ElMessage.error(res.message || "获取传感器列表失败");
|
ElMessage.error(res.message || "获取传感器列表失败");
|
||||||
// }
|
}
|
||||||
// } catch (error) {
|
} catch (error) {
|
||||||
// console.error("获取传感器列表失败:", error);
|
console.error("获取传感器列表失败:", error);
|
||||||
// ElMessage.error("获取传感器列表失败");
|
ElMessage.error("获取传感器列表失败");
|
||||||
// } finally {
|
} finally {
|
||||||
// loading.value = false;
|
loading.value = false;
|
||||||
// }
|
}
|
||||||
// };
|
};
|
||||||
|
|
||||||
// 处理页码改变
|
// 处理页码改变
|
||||||
const handleCurrentChange = (page) => {
|
const handleCurrentChange = (page) => {
|
||||||
pagination.value.page = page;
|
pagination.value.page = page;
|
||||||
// getSensorList();
|
getSensorList();
|
||||||
};
|
};
|
||||||
|
|
||||||
// 处理每页条数改变
|
// 处理每页条数改变
|
||||||
const handleSizeChange = (size) => {
|
const handleSizeChange = (size) => {
|
||||||
pagination.value.page_size = size;
|
pagination.value.page_size = size;
|
||||||
pagination.value.page = 1;
|
pagination.value.page = 1;
|
||||||
// getSensorList();
|
getSensorList();
|
||||||
};
|
};
|
||||||
|
|
||||||
// 删除传感器
|
// 删除传感器
|
||||||
@ -80,7 +64,7 @@ const handleDelete = async (id) => {
|
|||||||
const res = await deleteDevice(id);
|
const res = await deleteDevice(id);
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
ElMessage.success("删除成功");
|
ElMessage.success("删除成功");
|
||||||
// getSensorList();
|
getSensorList();
|
||||||
} else {
|
} else {
|
||||||
ElMessage.error(res.message || "删除失败");
|
ElMessage.error(res.message || "删除失败");
|
||||||
}
|
}
|
||||||
@ -130,17 +114,8 @@ const getTypeInfo = (type) => {
|
|||||||
|
|
||||||
// 格式化数据显示
|
// 格式化数据显示
|
||||||
const formatValue = (value, unit = "") => {
|
const formatValue = (value, unit = "") => {
|
||||||
if (value === "---") return value;
|
|
||||||
if (value === null || value === undefined) return "暂无数据";
|
if (value === null || value === undefined) return "暂无数据";
|
||||||
// 根据不同单位设置不同的小数位数
|
return `${value}${unit}`;
|
||||||
if (unit === "°C") {
|
|
||||||
return `${value.toFixed(1)}${unit}`;
|
|
||||||
} else if (unit === "%") {
|
|
||||||
return `${value.toFixed(0)}${unit}`;
|
|
||||||
} else if (unit === "μS/cm") {
|
|
||||||
return `${value.toFixed(0)}${unit}`;
|
|
||||||
}
|
|
||||||
return `${value.toFixed(0)}${unit}`;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 获取传感器类型名称
|
// 获取传感器类型名称
|
||||||
@ -152,144 +127,54 @@ const getSensorTypeName = (type, name) => {
|
|||||||
|
|
||||||
// 获取传感器数据项
|
// 获取传感器数据项
|
||||||
const getSensorDataItems = (sensor) => {
|
const getSensorDataItems = (sensor) => {
|
||||||
// 如果设备离线,所有数据项显示"---"
|
if (sensor.device_name.includes("CS616")) {
|
||||||
if (sensor.status === 0) {
|
// 土壤传感器数据项
|
||||||
return [{ label: "TDS值", value: "---", unit: "" }];
|
return [
|
||||||
|
{ label: "温度", value: sensor.data?.temp, unit: "°C" },
|
||||||
|
{ label: "湿度", value: sensor.data?.humi, unit: "%" },
|
||||||
|
{ label: "土壤湿度", value: sensor.data?.soil_adc, unit: "" },
|
||||||
|
{ label: "光照强度", value: sensor.data?.light_adc, unit: "" },
|
||||||
|
];
|
||||||
|
} else if (sensor.device_name.includes("RK500-13")) {
|
||||||
|
// 水质传感器数据项
|
||||||
|
return [
|
||||||
|
{ label: "温度", value: sensor.data?.temp, unit: "°C" },
|
||||||
|
{ label: "湿度", value: sensor.data?.humi, unit: "%" },
|
||||||
|
{ label: "水质电导率", value: sensor.data?.soil_adc, unit: "μS/cm" },
|
||||||
|
{ label: "光照强度", value: sensor.data?.light_adc, unit: "" },
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
// 默认数据项
|
||||||
// 设备在线时显示正常数据
|
return [
|
||||||
return [{ label: "TDS值", value: sensor.data?.tds, unit: "mg/L" }];
|
{ label: "温度", value: sensor.data?.temp, unit: "°C" },
|
||||||
|
{ label: "湿度", value: sensor.data?.humi, unit: "%" },
|
||||||
|
{ label: "光照强度", value: sensor.data?.light_adc, unit: "" },
|
||||||
|
{ label: "传感器值", value: sensor.data?.soil_adc, unit: "" },
|
||||||
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
// 添加数据刷新定时器引用
|
|
||||||
const dataRefreshTimers = ref({
|
|
||||||
h: null,
|
|
||||||
c: null,
|
|
||||||
f: null,
|
|
||||||
});
|
|
||||||
|
|
||||||
// 添加键盘事件处理函数
|
// 添加键盘事件处理函数
|
||||||
const handleKeyPress = (event) => {
|
const handleKeyPress = (event) => {
|
||||||
const key = event.key.toLowerCase();
|
if (event.key.toLowerCase() === "v") {
|
||||||
|
|
||||||
// 处理设备激活
|
|
||||||
if (key === "l") {
|
|
||||||
const isOnline = sensorList.value[0].status === 1;
|
|
||||||
sensorList.value = sensorList.value.map((sensor) => ({
|
|
||||||
...sensor,
|
|
||||||
status: isOnline ? 0 : 1,
|
|
||||||
updated_at: new Date().toISOString(),
|
|
||||||
data: {
|
|
||||||
tds: null,
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
// 清理所有定时器
|
|
||||||
Object.entries(dataRefreshTimers.value).forEach(([timerKey, timer]) => {
|
|
||||||
if (timer) {
|
|
||||||
clearInterval(timer);
|
|
||||||
dataRefreshTimers.value[timerKey] = null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ElNotification({
|
ElNotification({
|
||||||
title: isOnline ? "设备离线" : "设备上线",
|
title: "设备状态更新",
|
||||||
message: isOnline ? "设备已停止工作" : "设备开始工作",
|
message: "设备已上线",
|
||||||
type: isOnline ? "warning" : "success",
|
type: "success",
|
||||||
duration: 2000,
|
position: "top-right",
|
||||||
|
duration: 3000,
|
||||||
});
|
});
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果设备离线,不处理监测按键
|
|
||||||
if (sensorList.value.some((sensor) => sensor.status === 0)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 定义不同物质的数据模板
|
|
||||||
const substanceData = {
|
|
||||||
h: {
|
|
||||||
// 海水参考值 (1000-1370)
|
|
||||||
tds: 1185,
|
|
||||||
},
|
|
||||||
c: {
|
|
||||||
// 茶水参考值 (200-248)
|
|
||||||
tds: 224,
|
|
||||||
},
|
|
||||||
f: {
|
|
||||||
// 芬达参考值 (450-490)
|
|
||||||
tds: 470,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
if (key === "h" || key === "c" || key === "f") {
|
|
||||||
// 如果当前按键已经在刷新,则停止刷新
|
|
||||||
if (dataRefreshTimers.value[key]) {
|
|
||||||
clearInterval(dataRefreshTimers.value[key]);
|
|
||||||
dataRefreshTimers.value[key] = null;
|
|
||||||
// 停止监测时,清空数据显示为---
|
|
||||||
sensorList.value = sensorList.value.map((sensor) => ({
|
|
||||||
...sensor,
|
|
||||||
data: {
|
|
||||||
tds: null,
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 先停止其他正在运行的监测
|
|
||||||
Object.entries(dataRefreshTimers.value).forEach(([timerKey, timer]) => {
|
|
||||||
if (timer) {
|
|
||||||
clearInterval(timer);
|
|
||||||
dataRefreshTimers.value[timerKey] = null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 开始新的数据刷新
|
|
||||||
const startData = substanceData[key];
|
|
||||||
dataRefreshTimers.value[key] = setInterval(() => {
|
|
||||||
// 生成随机波动值,根据不同液体设置不同的波动范围
|
|
||||||
let randomValue;
|
|
||||||
switch (key) {
|
|
||||||
case "h":
|
|
||||||
// 海水范围:1000-1370
|
|
||||||
randomValue = 1000 + Math.random() * 370;
|
|
||||||
break;
|
|
||||||
case "c":
|
|
||||||
// 茶水范围:200-248
|
|
||||||
randomValue = 200 + Math.random() * 48;
|
|
||||||
break;
|
|
||||||
case "f":
|
|
||||||
// 芬达范围:450-490
|
|
||||||
randomValue = 450 + Math.random() * 40;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新传感器数据
|
|
||||||
sensorList.value = sensorList.value.map((sensor) => ({
|
|
||||||
...sensor,
|
|
||||||
status: 1,
|
|
||||||
updated_at: new Date().toISOString(),
|
|
||||||
data: {
|
|
||||||
tds: randomValue,
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
}, 1000); // 每秒更新一次
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 组件挂载时添加键盘事件监听
|
// 组件挂载时添加键盘事件监听
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
getSensorList();
|
||||||
window.addEventListener("keypress", handleKeyPress);
|
window.addEventListener("keypress", handleKeyPress);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 组件卸载时移除键盘事件监听
|
// 组件卸载时移除键盘事件监听
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
window.removeEventListener("keypress", handleKeyPress);
|
window.removeEventListener("keypress", handleKeyPress);
|
||||||
// 清理所有数据刷新定时器
|
|
||||||
Object.values(dataRefreshTimers.value).forEach((timer) => {
|
|
||||||
if (timer) clearInterval(timer);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -349,28 +234,9 @@ onUnmounted(() => {
|
|||||||
v-for="(item, index) in getSensorDataItems(sensor)"
|
v-for="(item, index) in getSensorDataItems(sensor)"
|
||||||
:key="index"
|
:key="index"
|
||||||
class="data-item"
|
class="data-item"
|
||||||
style="
|
|
||||||
grid-column: 1 / -1;
|
|
||||||
background: #fff;
|
|
||||||
padding: 20px 40px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
"
|
|
||||||
>
|
>
|
||||||
<div
|
<div class="data-value">{{ formatValue(item.value, item.unit) }}</div>
|
||||||
class="data-value"
|
<div class="data-label">{{ item.label }}</div>
|
||||||
style="font-size: 32px; color: #1890ff; text-align: center"
|
|
||||||
>
|
|
||||||
{{ formatValue(item.value, item.unit) }}
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="data-label"
|
|
||||||
style="font-size: 14px; margin-top: 8px; text-align: center"
|
|
||||||
>
|
|
||||||
{{ item.label }}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -383,6 +249,12 @@ onUnmounted(() => {
|
|||||||
: "暂无数据"
|
: "暂无数据"
|
||||||
}}
|
}}
|
||||||
</div>
|
</div>
|
||||||
|
<div class="actions">
|
||||||
|
<el-button type="primary" link>编辑</el-button>
|
||||||
|
<el-button type="danger" link @click="handleDelete(sensor.id)"
|
||||||
|
>删除</el-button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -452,6 +324,11 @@ onUnmounted(() => {
|
|||||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.offline {
|
||||||
|
opacity: 0.8;
|
||||||
|
background: #f5f7fa;
|
||||||
|
}
|
||||||
|
|
||||||
.card-header {
|
.card-header {
|
||||||
padding: 16px 20px;
|
padding: 16px 20px;
|
||||||
border-bottom: 1px solid #f0f2f5;
|
border-bottom: 1px solid #f0f2f5;
|
||||||
@ -545,11 +422,6 @@ onUnmounted(() => {
|
|||||||
padding: 12px;
|
padding: 12px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
height: 76px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.data-value {
|
.data-value {
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
@ -557,18 +429,11 @@ onUnmounted(() => {
|
|||||||
color: #409eff;
|
color: #409eff;
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
font-family: "DIN Alternate", sans-serif;
|
font-family: "DIN Alternate", sans-serif;
|
||||||
transition: all 0.3s ease;
|
|
||||||
min-height: 30px;
|
|
||||||
line-height: 30px;
|
|
||||||
width: 100%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.data-label {
|
.data-label {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: #909399;
|
color: #909399;
|
||||||
min-height: 18px;
|
|
||||||
line-height: 18px;
|
|
||||||
width: 100%;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -592,25 +457,10 @@ onUnmounted(() => {
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.offline {
|
.actions {
|
||||||
opacity: 0.8;
|
|
||||||
background: #f5f7fa;
|
|
||||||
|
|
||||||
.data-section {
|
|
||||||
.data-grid {
|
|
||||||
.data-item {
|
|
||||||
.data-value {
|
|
||||||
color: #909399 !important;
|
|
||||||
font-size: 18px !important;
|
|
||||||
letter-spacing: 2px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
gap: 12px;
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,15 +1,15 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted, onUnmounted } from "vue";
|
import { ref, onMounted, onUnmounted } from "vue";
|
||||||
import * as echarts from "echarts";
|
import * as echarts from "echarts";
|
||||||
import { markRaw } from "vue";
|
import { markRaw } from 'vue'
|
||||||
import {
|
import {
|
||||||
Monitor,
|
Monitor,
|
||||||
DataAnalysis,
|
DataAnalysis,
|
||||||
Location,
|
Location,
|
||||||
Document,
|
Document,
|
||||||
Warning,
|
Warning
|
||||||
} from "@element-plus/icons-vue";
|
} from "@element-plus/icons-vue";
|
||||||
import { getSpeciesStatistics, getPatrolStatistics, getDeviceList } from "@/api/dashboard";
|
import { getSpeciesStatistics, getPatrolStatistics, getDeviceList } from '@/api/dashboard'
|
||||||
|
|
||||||
// 使用 markRaw 包装图标组件
|
// 使用 markRaw 包装图标组件
|
||||||
const icons = {
|
const icons = {
|
||||||
@ -17,341 +17,334 @@ const icons = {
|
|||||||
DataAnalysis: markRaw(DataAnalysis),
|
DataAnalysis: markRaw(DataAnalysis),
|
||||||
Location: markRaw(Location),
|
Location: markRaw(Location),
|
||||||
Document: markRaw(Document),
|
Document: markRaw(Document),
|
||||||
Warning: markRaw(Warning),
|
Warning: markRaw(Warning)
|
||||||
};
|
};
|
||||||
|
|
||||||
// 物种类别选项
|
// 物种类别选项
|
||||||
const categoryOptions = [
|
const categoryOptions = [
|
||||||
{ label: "鸟类", value: "bird" },
|
{ label: '鸟类', value: 'bird' },
|
||||||
{ label: "哺乳类", value: "mammal" },
|
{ label: '哺乳类', value: 'mammal' },
|
||||||
{ label: "鱼类", value: "fish" },
|
{ label: '鱼类', value: 'fish' },
|
||||||
{ label: "两栖类", value: "amphibian" },
|
{ label: '两栖类', value: 'amphibian' },
|
||||||
{ label: "爬行类", value: "reptile" },
|
{ label: '爬行类', value: 'reptile' },
|
||||||
{ label: "昆虫类", value: "insect" },
|
{ label: '昆虫类', value: 'insect' },
|
||||||
{ label: "植物", value: "plant" },
|
{ label: '植物', value: 'plant' }
|
||||||
];
|
]
|
||||||
|
|
||||||
// 保护等级选项
|
// 保护等级选项
|
||||||
const protectionLevelOptions = [
|
const protectionLevelOptions = [
|
||||||
{ label: "国家一级", value: "national_first" },
|
{ label: '国家一级', value: 'national_first' },
|
||||||
{ label: "国家二级", value: "national_second" },
|
{ label: '国家二级', value: 'national_second' },
|
||||||
{ label: "省级", value: "provincial" },
|
{ label: '省级', value: 'provincial' },
|
||||||
{ label: "普通", value: "normal" },
|
{ label: '普通', value: 'normal' }
|
||||||
];
|
]
|
||||||
|
|
||||||
// 图表实例
|
// 图表实例
|
||||||
const categoryChartRef = ref(null);
|
const categoryChartRef = ref(null)
|
||||||
let categoryChart = null;
|
let categoryChart = null
|
||||||
const protectionChartRef = ref(null);
|
const protectionChartRef = ref(null)
|
||||||
let protectionChart = null;
|
let protectionChart = null
|
||||||
|
|
||||||
// 统计卡片数据
|
// 统计卡片数据
|
||||||
const statsCards = ref([
|
const statsCards = ref([
|
||||||
{
|
{
|
||||||
title: "物种监测",
|
title: '物种监测',
|
||||||
icon: icons.Monitor,
|
icon: icons.Monitor,
|
||||||
value: "0",
|
value: '0',
|
||||||
unit: "种",
|
unit: '种',
|
||||||
change: { value: "0", label: "今日新增" },
|
change: { value: '0', label: '今日新增' },
|
||||||
color: "#1890FF",
|
color: '#1890FF',
|
||||||
bgColor: "linear-gradient(120deg, #0072FF 0%, #00C6FF 100%)",
|
bgColor: 'linear-gradient(120deg, #0072FF 0%, #00C6FF 100%)',
|
||||||
features: ["实时监测", "智能识别", "行为分析", "分布追踪"],
|
features: ['实时监测', '智能识别', '行为分析', '分布追踪']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "环境监测",
|
title: '环境监测',
|
||||||
icon: icons.DataAnalysis,
|
icon: icons.DataAnalysis,
|
||||||
value: "2",
|
value: '2',
|
||||||
unit: "点",
|
unit: '点',
|
||||||
change: { value: "2", label: "异常" },
|
change: { value: '2', label: '异常' },
|
||||||
color: "#F5222D",
|
color: '#F5222D',
|
||||||
bgColor: "linear-gradient(120deg, #FF416C 0%, #FF4B2B 100%)",
|
bgColor: 'linear-gradient(120deg, #FF416C 0%, #FF4B2B 100%)',
|
||||||
features: ["水质监测", "空气监测", "土壤监测", "气象监测"],
|
features: ['水质监测', '空气监测', '土壤监测', '气象监测']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "巡护任务",
|
title: '巡护任务',
|
||||||
icon: icons.Location,
|
icon: icons.Location,
|
||||||
value: "0",
|
value: '0',
|
||||||
unit: "个",
|
unit: '个',
|
||||||
change: { value: "0%", label: "完成率" },
|
change: { value: '0%', label: '完成率' },
|
||||||
color: "#52C41A",
|
color: '#52C41A',
|
||||||
bgColor: "linear-gradient(120deg, #00B09B 0%, #96C93D 100%)",
|
bgColor: 'linear-gradient(120deg, #00B09B 0%, #96C93D 100%)',
|
||||||
features: ["智能派单", "轨迹记录", "实时通讯", "数据采集"],
|
features: ['智能派单', '轨迹记录', '实时通讯', '数据采集']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "设备状态",
|
title: '设备状态',
|
||||||
icon: icons.Monitor,
|
icon: icons.Monitor,
|
||||||
value: "0",
|
value: '0',
|
||||||
unit: "台",
|
unit: '台',
|
||||||
change: { value: "0%", label: "在线率" },
|
change: { value: '0%', label: '在线率' },
|
||||||
color: "#722ED1",
|
color: '#722ED1',
|
||||||
bgColor: "linear-gradient(120deg, #7F00FF 0%, #E100FF 100%)",
|
bgColor: 'linear-gradient(120deg, #7F00FF 0%, #E100FF 100%)',
|
||||||
features: ["状态监控", "故障预警", "维护管理", "性能分析"],
|
features: ['状态监控', '故障预警', '维护管理', '性能分析']
|
||||||
},
|
}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// 设备状态统计数据
|
// 设备状态统计数据
|
||||||
const deviceData = ref({
|
const deviceData = ref({
|
||||||
total: 0,
|
total: 0,
|
||||||
online: 0,
|
online: 0
|
||||||
});
|
})
|
||||||
|
|
||||||
// 初始化物种类别图表
|
// 初始化物种类别图表
|
||||||
const initCategoryChart = () => {
|
const initCategoryChart = () => {
|
||||||
if (!categoryChartRef.value) return;
|
if (!categoryChartRef.value) return
|
||||||
|
|
||||||
categoryChart = echarts.init(categoryChartRef.value);
|
categoryChart = echarts.init(categoryChartRef.value)
|
||||||
const option = {
|
const option = {
|
||||||
tooltip: {
|
tooltip: {
|
||||||
trigger: "item",
|
trigger: 'item',
|
||||||
formatter: "{b}: {c}种 ({d}%)",
|
formatter: '{b}: {c}种 ({d}%)'
|
||||||
},
|
},
|
||||||
legend: {
|
legend: {
|
||||||
orient: "vertical",
|
orient: 'vertical',
|
||||||
left: "left",
|
left: 'left',
|
||||||
top: "middle",
|
top: 'middle',
|
||||||
textStyle: {
|
textStyle: {
|
||||||
color: "#303133",
|
color: '#303133'
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
series: [
|
series: [
|
||||||
{
|
{
|
||||||
name: "物种数量",
|
name: '物种数量',
|
||||||
type: "pie",
|
type: 'pie',
|
||||||
radius: ["40%", "70%"],
|
radius: ['40%', '70%'],
|
||||||
center: ["60%", "50%"],
|
center: ['60%', '50%'],
|
||||||
avoidLabelOverlap: true,
|
avoidLabelOverlap: true,
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
borderRadius: 10,
|
borderRadius: 10,
|
||||||
borderColor: "#fff",
|
borderColor: '#fff',
|
||||||
borderWidth: 2,
|
borderWidth: 2
|
||||||
},
|
},
|
||||||
label: {
|
label: {
|
||||||
show: true,
|
show: true,
|
||||||
formatter: "{b}: {c}种",
|
formatter: '{b}: {c}种'
|
||||||
},
|
},
|
||||||
emphasis: {
|
emphasis: {
|
||||||
label: {
|
label: {
|
||||||
show: true,
|
show: true,
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontWeight: "bold",
|
fontWeight: 'bold'
|
||||||
},
|
},
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
shadowBlur: 10,
|
shadowBlur: 10,
|
||||||
shadowOffsetX: 0,
|
shadowOffsetX: 0,
|
||||||
shadowColor: "rgba(0, 0, 0, 0.5)",
|
shadowColor: 'rgba(0, 0, 0, 0.5)'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
data: []
|
||||||
data: [],
|
}
|
||||||
},
|
]
|
||||||
],
|
}
|
||||||
};
|
|
||||||
|
|
||||||
categoryChart.setOption(option);
|
categoryChart.setOption(option)
|
||||||
};
|
}
|
||||||
|
|
||||||
// 更新图表数据
|
// 更新图表数据
|
||||||
const updateCategoryChart = (data) => {
|
const updateCategoryChart = (data) => {
|
||||||
if (!categoryChart) return;
|
if (!categoryChart) return
|
||||||
|
|
||||||
// 物种类别图表数据
|
// 物种类别图表数据
|
||||||
const categoryData = Object.entries(data.categories)
|
const categoryData = Object.entries(data.categories)
|
||||||
.filter(([_, count]) => count.total_count > 0)
|
.filter(([_, count]) => count.total_count > 0)
|
||||||
.map(([category, count]) => ({
|
.map(([category, count]) => ({
|
||||||
name: categoryOptions.find((item) => item.value === category)?.label || category,
|
name: categoryOptions.find(item => item.value === category)?.label || category,
|
||||||
value: parseInt(count.total_count),
|
value: parseInt(count.total_count)
|
||||||
}))
|
}))
|
||||||
.sort((a, b) => b.value - a.value);
|
.sort((a, b) => b.value - a.value)
|
||||||
|
|
||||||
categoryChart.setOption({
|
categoryChart.setOption({
|
||||||
series: [
|
series: [{
|
||||||
{
|
data: categoryData
|
||||||
data: categoryData,
|
}]
|
||||||
},
|
})
|
||||||
],
|
}
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// 获取物种统计数据
|
// 获取物种统计数据
|
||||||
const fetchSpeciesData = async () => {
|
const fetchSpeciesData = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await getSpeciesStatistics();
|
const res = await getSpeciesStatistics()
|
||||||
if (res.success && res.data) {
|
if (res.success && res.data) {
|
||||||
// 计算总物种数和今日新增数
|
// 计算总物种数和今日新增数
|
||||||
const totalSpecies = Object.values(res.data.categories).reduce(
|
const totalSpecies = Object.values(res.data.categories).reduce((sum, category) =>
|
||||||
(sum, category) => sum + (parseInt(category.total_count) || 0),
|
sum + (parseInt(category.total_count) || 0), 0)
|
||||||
0
|
const todayNew = Object.values(res.data.categories).reduce((sum, category) =>
|
||||||
);
|
sum + (parseInt(category.today_count) || 0), 0)
|
||||||
const todayNew = Object.values(res.data.categories).reduce(
|
|
||||||
(sum, category) => sum + (parseInt(category.today_count) || 0),
|
|
||||||
0
|
|
||||||
);
|
|
||||||
|
|
||||||
// 更新物种监测卡片
|
// 更新物种监测卡片
|
||||||
statsCards.value[0].value = String(totalSpecies || 0);
|
statsCards.value[0].value = String(totalSpecies || 0)
|
||||||
statsCards.value[0].change.value = `+${todayNew || 0}`;
|
statsCards.value[0].change.value = `+${todayNew || 0}`
|
||||||
|
|
||||||
// 更新物种分布图表
|
// 更新物种分布图表
|
||||||
updateCategoryChart(res.data);
|
updateCategoryChart(res.data)
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("获取物种统计数据失败:", error);
|
console.error('获取物种统计数据失败:', error)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
// 获取巡护任务统计数据
|
// 获取巡护任务统计数据
|
||||||
const fetchPatrolData = async () => {
|
const fetchPatrolData = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await getPatrolStatistics();
|
const res = await getPatrolStatistics()
|
||||||
if (res.success && res.data) {
|
if (res.success && res.data) {
|
||||||
const { overview } = res.data;
|
const { overview } = res.data
|
||||||
const progress = ((overview.completed_count / overview.total_count) * 100).toFixed(1);
|
const progress = ((overview.completed_count / overview.total_count) * 100).toFixed(1)
|
||||||
|
|
||||||
// 更新巡护任务卡片
|
// 更新巡护任务卡片
|
||||||
statsCards.value[2].value = String(overview.total_count);
|
statsCards.value[2].value = String(overview.total_count)
|
||||||
statsCards.value[2].change.value = `${progress}%`;
|
statsCards.value[2].change.value = `${progress}%`
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("获取巡护任务统计数据失败:", error);
|
console.error('获取巡护任务统计数据失败:', error)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
// 获取设备列表数据
|
// 获取设备列表数据
|
||||||
const fetchDeviceData = async () => {
|
const fetchDeviceData = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await getDeviceList();
|
const res = await getDeviceList()
|
||||||
if (res.success && res.data?.list) {
|
if (res.success && res.data?.list) {
|
||||||
const deviceList = res.data.list;
|
const deviceList = res.data.list
|
||||||
deviceData.value = {
|
deviceData.value = {
|
||||||
total: deviceList.length,
|
total: deviceList.length,
|
||||||
online: deviceList.filter((device) => device.status?.code === 1).length,
|
online: deviceList.filter(device => device.status?.code === 1).length
|
||||||
};
|
}
|
||||||
|
|
||||||
// 更新设备状态卡片
|
// 更新设备状态卡片
|
||||||
statsCards.value[3].value = String(deviceData.value.total);
|
statsCards.value[3].value = String(deviceData.value.total)
|
||||||
statsCards.value[3].change.value = `${(
|
statsCards.value[3].change.value = `${((deviceData.value.online / deviceData.value.total) * 100).toFixed(1)}%`
|
||||||
(deviceData.value.online / deviceData.value.total) *
|
|
||||||
100
|
|
||||||
).toFixed(1)}%`;
|
|
||||||
// 更新设备状态特性
|
// 更新设备状态特性
|
||||||
statsCards.value[3].features = [
|
statsCards.value[3].features = [
|
||||||
`在线: ${deviceData.value.online}台`,
|
`在线: ${deviceData.value.online}台`,
|
||||||
`离线: ${deviceData.value.total - deviceData.value.online}台`,
|
`离线: ${deviceData.value.total - deviceData.value.online}台`,
|
||||||
"故障预警",
|
'故障预警',
|
||||||
"性能分析",
|
'性能分析'
|
||||||
];
|
]
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("获取设备列表数据失败:", error);
|
console.error('获取设备列表数据失败:', error)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
// 初始化保护等级图表
|
// 初始化保护等级图表
|
||||||
const initProtectionChart = () => {
|
const initProtectionChart = () => {
|
||||||
if (!protectionChartRef.value) return;
|
if (!protectionChartRef.value) return
|
||||||
|
|
||||||
protectionChart = echarts.init(protectionChartRef.value);
|
protectionChart = echarts.init(protectionChartRef.value)
|
||||||
const option = {
|
const option = {
|
||||||
title: {
|
title: {
|
||||||
text: "保护等级统计",
|
text: '保护等级统计',
|
||||||
textStyle: {
|
textStyle: {
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
fontWeight: 500,
|
fontWeight: 500,
|
||||||
color: "#303133",
|
color: '#303133'
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
tooltip: {
|
tooltip: {
|
||||||
trigger: "axis",
|
trigger: 'axis',
|
||||||
axisPointer: {
|
axisPointer: {
|
||||||
type: "shadow",
|
type: 'shadow'
|
||||||
},
|
},
|
||||||
formatter: "{b}: {c}种",
|
formatter: '{b}: {c}种'
|
||||||
},
|
},
|
||||||
grid: {
|
grid: {
|
||||||
left: "3%",
|
left: '3%',
|
||||||
right: "4%",
|
right: '4%',
|
||||||
bottom: "10%",
|
bottom: '10%',
|
||||||
containLabel: true,
|
containLabel: true
|
||||||
},
|
},
|
||||||
xAxis: {
|
xAxis: {
|
||||||
type: "category",
|
type: 'category',
|
||||||
data: [],
|
data: [],
|
||||||
axisLabel: {
|
axisLabel: {
|
||||||
interval: 0,
|
interval: 0,
|
||||||
rotate: 30,
|
rotate: 30
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
yAxis: {
|
yAxis: {
|
||||||
type: "value",
|
type: 'value',
|
||||||
name: "物种数量",
|
name: '物种数量',
|
||||||
minInterval: 1,
|
minInterval: 1
|
||||||
},
|
},
|
||||||
series: [
|
series: [
|
||||||
{
|
{
|
||||||
name: "物种数量",
|
name: '物种数量',
|
||||||
type: "bar",
|
type: 'bar',
|
||||||
barWidth: "40%",
|
barWidth: '40%',
|
||||||
data: [],
|
data: [],
|
||||||
label: {
|
label: {
|
||||||
show: true,
|
show: true,
|
||||||
position: "top",
|
position: 'top',
|
||||||
formatter: "{c}种",
|
formatter: '{c}种'
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
],
|
]
|
||||||
};
|
}
|
||||||
|
|
||||||
protectionChart.setOption(option);
|
protectionChart.setOption(option)
|
||||||
};
|
}
|
||||||
|
|
||||||
// 更新保护等级图表数据
|
// 更新保护等级图表数据
|
||||||
const updateProtectionChart = (data) => {
|
const updateProtectionChart = (data) => {
|
||||||
if (!protectionChart) return;
|
if (!protectionChart) return
|
||||||
|
|
||||||
// 保护等级图表数据
|
// 保护等级图表数据
|
||||||
const protectionData = Object.entries(data.protection_levels)
|
const protectionData = Object.entries(data.protection_levels)
|
||||||
.filter(([_, count]) => count > 0)
|
.filter(([_, count]) => count > 0)
|
||||||
.map(([level, count]) => ({
|
.map(([level, count]) => ({
|
||||||
name: protectionLevelOptions.find((item) => item.value === level)?.label || level,
|
name: protectionLevelOptions.find(item => item.value === level)?.label || level,
|
||||||
value: count,
|
value: count
|
||||||
}))
|
}))
|
||||||
.sort((a, b) => b.value - a.value);
|
.sort((a, b) => b.value - a.value)
|
||||||
|
|
||||||
protectionChart.setOption({
|
protectionChart.setOption({
|
||||||
xAxis: {
|
xAxis: {
|
||||||
data: protectionData.map((item) => item.name),
|
data: protectionData.map(item => item.name)
|
||||||
},
|
},
|
||||||
series: [
|
series: [{
|
||||||
{
|
data: protectionData.map(item => ({
|
||||||
data: protectionData.map((item) => ({
|
|
||||||
value: item.value,
|
value: item.value,
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||||
{ offset: 0, color: "#83bff6" },
|
{ offset: 0, color: '#83bff6' },
|
||||||
{ offset: 0.5, color: "#409EFF" },
|
{ offset: 0.5, color: '#409EFF' },
|
||||||
{ offset: 1, color: "#2c76c5" },
|
{ offset: 1, color: '#2c76c5' }
|
||||||
]),
|
])
|
||||||
},
|
}
|
||||||
})),
|
}))
|
||||||
},
|
}]
|
||||||
],
|
})
|
||||||
});
|
}
|
||||||
};
|
|
||||||
|
|
||||||
// 初始化数据
|
// 初始化数据
|
||||||
const initData = async () => {
|
const initData = async () => {
|
||||||
try {
|
try {
|
||||||
await Promise.all([fetchSpeciesData(), fetchPatrolData(), fetchDeviceData()]);
|
await Promise.all([
|
||||||
|
fetchSpeciesData(),
|
||||||
|
fetchPatrolData(),
|
||||||
|
fetchDeviceData()
|
||||||
|
])
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("初始化数据失败:", error);
|
console.error('初始化数据失败:', error)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
// 自动刷新数据
|
// 自动刷新数据
|
||||||
let timer = null;
|
let timer = null
|
||||||
const startAutoRefresh = () => {
|
const startAutoRefresh = () => {
|
||||||
fetchStatisticsData();
|
fetchStatisticsData()
|
||||||
timer = setInterval(fetchStatisticsData, 60000); // 每分钟更新一次
|
timer = setInterval(fetchStatisticsData, 60000) // 每分钟更新一次
|
||||||
};
|
}
|
||||||
|
|
||||||
// 初始化趋势图表
|
// 初始化趋势图表
|
||||||
const initTrendChart = () => {
|
const initTrendChart = () => {
|
||||||
@ -365,129 +358,129 @@ const initTrendChart = () => {
|
|||||||
textStyle: {
|
textStyle: {
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
fontWeight: 500,
|
fontWeight: 500,
|
||||||
color: "#303133",
|
color: '#303133'
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
tooltip: {
|
tooltip: {
|
||||||
trigger: "axis",
|
trigger: 'axis',
|
||||||
backgroundColor: "rgba(255, 255, 255, 0.95)",
|
backgroundColor: 'rgba(255, 255, 255, 0.95)',
|
||||||
borderColor: "#eee",
|
borderColor: '#eee',
|
||||||
padding: [10, 15],
|
padding: [10, 15],
|
||||||
textStyle: {
|
textStyle: {
|
||||||
color: "#666",
|
color: '#666'
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
legend: {
|
legend: {
|
||||||
data: ["物种数量", "监测数据"],
|
data: ['物种数量', '监测数据'],
|
||||||
right: 20,
|
right: 20,
|
||||||
top: 10,
|
top: 10,
|
||||||
textStyle: {
|
textStyle: {
|
||||||
color: "#666",
|
color: '#666'
|
||||||
},
|
},
|
||||||
itemWidth: 12,
|
itemWidth: 12,
|
||||||
itemHeight: 12,
|
itemHeight: 12,
|
||||||
itemGap: 20,
|
itemGap: 20
|
||||||
},
|
},
|
||||||
grid: {
|
grid: {
|
||||||
left: "3%",
|
left: '3%',
|
||||||
right: "4%",
|
right: '4%',
|
||||||
bottom: "3%",
|
bottom: '3%',
|
||||||
containLabel: true,
|
containLabel: true
|
||||||
},
|
},
|
||||||
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'
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
axisTick: {
|
axisTick: {
|
||||||
show: false,
|
show: false
|
||||||
},
|
},
|
||||||
axisLabel: {
|
axisLabel: {
|
||||||
color: "#909399",
|
color: '#909399'
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
yAxis: {
|
yAxis: {
|
||||||
type: "value",
|
type: 'value',
|
||||||
splitLine: {
|
splitLine: {
|
||||||
lineStyle: {
|
lineStyle: {
|
||||||
color: "#EBEEF5",
|
color: '#EBEEF5',
|
||||||
type: "dashed",
|
type: 'dashed'
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
axisLabel: {
|
axisLabel: {
|
||||||
color: "#909399",
|
color: '#909399'
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
series: [
|
series: [
|
||||||
{
|
{
|
||||||
name: "物种数量",
|
name: '物种数量',
|
||||||
type: "line",
|
type: 'line',
|
||||||
smooth: true,
|
smooth: true,
|
||||||
symbolSize: 8,
|
symbolSize: 8,
|
||||||
lineStyle: {
|
lineStyle: {
|
||||||
width: 3,
|
width: 3,
|
||||||
color: "#409EFF",
|
color: '#409EFF'
|
||||||
},
|
},
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
color: "#409EFF",
|
color: '#409EFF',
|
||||||
borderColor: "#fff",
|
borderColor: '#fff',
|
||||||
borderWidth: 2,
|
borderWidth: 2
|
||||||
},
|
},
|
||||||
areaStyle: {
|
areaStyle: {
|
||||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||||
{ offset: 0, color: "rgba(64, 158, 255, 0.2)" },
|
{ offset: 0, color: 'rgba(64, 158, 255, 0.2)' },
|
||||||
{ offset: 1, color: "rgba(64, 158, 255, 0)" },
|
{ offset: 1, color: 'rgba(64, 158, 255, 0)' }
|
||||||
]),
|
])
|
||||||
},
|
},
|
||||||
emphasis: {
|
emphasis: {
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
borderWidth: 3,
|
borderWidth: 3,
|
||||||
shadowColor: "rgba(64, 158, 255, 0.5)",
|
shadowColor: 'rgba(64, 158, 255, 0.5)',
|
||||||
shadowBlur: 10,
|
shadowBlur: 10
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
data: [120, 132, 101, 134, 90, 230, 210]
|
||||||
data: [120, 132, 101, 134, 90, 230, 210],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "监测数据",
|
name: '监测数据',
|
||||||
type: "line",
|
type: 'line',
|
||||||
smooth: true,
|
smooth: true,
|
||||||
symbolSize: 8,
|
symbolSize: 8,
|
||||||
lineStyle: {
|
lineStyle: {
|
||||||
width: 3,
|
width: 3,
|
||||||
color: "#67C23A",
|
color: '#67C23A'
|
||||||
},
|
},
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
color: "#67C23A",
|
color: '#67C23A',
|
||||||
borderColor: "#fff",
|
borderColor: '#fff',
|
||||||
borderWidth: 2,
|
borderWidth: 2
|
||||||
},
|
},
|
||||||
areaStyle: {
|
areaStyle: {
|
||||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||||
{ offset: 0, color: "rgba(103, 194, 58, 0.2)" },
|
{ offset: 0, color: 'rgba(103, 194, 58, 0.2)' },
|
||||||
{ offset: 1, color: "rgba(103, 194, 58, 0)" },
|
{ offset: 1, color: 'rgba(103, 194, 58, 0)' }
|
||||||
]),
|
])
|
||||||
},
|
},
|
||||||
emphasis: {
|
emphasis: {
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
borderWidth: 3,
|
borderWidth: 3,
|
||||||
shadowColor: "rgba(103, 194, 58, 0.5)",
|
shadowColor: 'rgba(103, 194, 58, 0.5)',
|
||||||
shadowBlur: 10,
|
shadowBlur: 10
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
data: [220, 182, 191, 234, 290, 330, 310]
|
||||||
data: [220, 182, 191, 234, 290, 330, 310],
|
}
|
||||||
},
|
]
|
||||||
],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
myChart.setOption(option);
|
myChart.setOption(option);
|
||||||
|
|
||||||
// 监听窗口大小变化
|
// 监听窗口大小变化
|
||||||
window.addEventListener("resize", () => {
|
window.addEventListener('resize', () => {
|
||||||
myChart.resize();
|
myChart.resize();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -603,72 +596,68 @@ const initDistributionChart = () => {
|
|||||||
// 获取统计数据
|
// 获取统计数据
|
||||||
const fetchStatisticsData = async () => {
|
const fetchStatisticsData = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await getSpeciesStatistics();
|
const res = await getSpeciesStatistics()
|
||||||
if (res.success && res.data) {
|
if (res.success && res.data) {
|
||||||
// 更新物种总数卡片
|
// 更新物种总数卡片
|
||||||
const totalSpecies = Object.values(res.data.categories).reduce(
|
const totalSpecies = Object.values(res.data.categories).reduce(
|
||||||
(sum, item) => sum + (parseInt(item.total_count) || 0),
|
(sum, item) => sum + (parseInt(item.total_count) || 0), 0
|
||||||
0
|
)
|
||||||
);
|
|
||||||
const newSpecies = Object.values(res.data.categories).reduce(
|
const newSpecies = Object.values(res.data.categories).reduce(
|
||||||
(sum, item) => sum + (parseInt(item.today_count) || 0),
|
(sum, item) => sum + (parseInt(item.today_count) || 0), 0
|
||||||
0
|
)
|
||||||
);
|
statsCards.value[0].value = String(totalSpecies || 0)
|
||||||
statsCards.value[0].value = String(totalSpecies || 0);
|
statsCards.value[0].change.value = `+${newSpecies || 0}`
|
||||||
statsCards.value[0].change.value = `+${newSpecies || 0}`;
|
|
||||||
|
|
||||||
// 更新物种分布图表
|
// 更新物种分布图表
|
||||||
updateCategoryChart(res.data);
|
updateCategoryChart(res.data)
|
||||||
|
|
||||||
// 更新保护等级图表
|
// 更新保护等级图表
|
||||||
updateProtectionChart(res.data);
|
updateProtectionChart(res.data)
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("获取统计数据失败:", error);
|
console.error('获取统计数据失败:', error)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
initData();
|
initData()
|
||||||
startAutoRefresh();
|
startAutoRefresh()
|
||||||
initTrendChart();
|
initTrendChart()
|
||||||
initDistributionChart();
|
initDistributionChart()
|
||||||
initCategoryChart();
|
initCategoryChart()
|
||||||
initProtectionChart();
|
initProtectionChart()
|
||||||
window.addEventListener("resize", () => {
|
window.addEventListener('resize', () => {
|
||||||
categoryChart?.resize();
|
categoryChart?.resize()
|
||||||
protectionChart?.resize();
|
protectionChart?.resize()
|
||||||
});
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
if (timer) {
|
if (timer) {
|
||||||
clearInterval(timer);
|
clearInterval(timer)
|
||||||
}
|
}
|
||||||
if (categoryChart) {
|
if (categoryChart) {
|
||||||
categoryChart.dispose();
|
categoryChart.dispose()
|
||||||
categoryChart = null;
|
categoryChart = null
|
||||||
}
|
}
|
||||||
if (protectionChart) {
|
if (protectionChart) {
|
||||||
protectionChart.dispose();
|
protectionChart.dispose()
|
||||||
protectionChart = null;
|
protectionChart = null
|
||||||
}
|
}
|
||||||
window.removeEventListener("resize", () => {
|
window.removeEventListener('resize', () => {
|
||||||
categoryChart?.resize();
|
categoryChart?.resize()
|
||||||
protectionChart?.resize();
|
protectionChart?.resize()
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="dashboard-container">
|
<div class="dashboard-container">
|
||||||
<div class="stats-grid">
|
<div class="stats-grid">
|
||||||
<div
|
<div v-for="card in statsCards"
|
||||||
v-for="card in statsCards"
|
|
||||||
:key="card.title"
|
:key="card.title"
|
||||||
class="stats-card"
|
class="stats-card"
|
||||||
:style="{ backgroundColor: card.bgColor }"
|
:style="{ backgroundColor: card.bgColor }">
|
||||||
>
|
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<el-icon :size="20" :color="card.color">
|
<el-icon :size="20" :color="card.color">
|
||||||
@ -682,22 +671,17 @@ onUnmounted(() => {
|
|||||||
{{ card.value }}
|
{{ card.value }}
|
||||||
<span class="unit">{{ card.unit }}</span>
|
<span class="unit">{{ card.unit }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div class="change-value"
|
||||||
class="change-value"
|
:style="{ color: card.change.value.includes('+') ? '#67C23A' :
|
||||||
:style="{
|
card.change.value.includes('%') ? card.color : '#F56C6C' }">
|
||||||
color: card.change.value.includes('+')
|
|
||||||
? '#67C23A'
|
|
||||||
: card.change.value.includes('%')
|
|
||||||
? card.color
|
|
||||||
: '#F56C6C',
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
{{ card.change.value }}
|
{{ card.change.value }}
|
||||||
<span class="label">{{ card.change.label }}</span>
|
<span class="label">{{ card.change.label }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-footer">
|
<div class="card-footer">
|
||||||
<div v-for="feature in card.features" :key="feature" class="feature-item">
|
<div v-for="feature in card.features"
|
||||||
|
:key="feature"
|
||||||
|
class="feature-item">
|
||||||
{{ feature }}
|
{{ feature }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -719,66 +703,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">A3、B5区</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 +883,7 @@ 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>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -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]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|||||||
@ -34,10 +34,10 @@ const initChart = async () => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
grid: {
|
grid: {
|
||||||
top: '3%',
|
top: '8%',
|
||||||
right: '15%',
|
right: '15%',
|
||||||
bottom: '3%',
|
bottom: '15%',
|
||||||
left: '5%',
|
left: '15%',
|
||||||
containLabel: true
|
containLabel: true
|
||||||
},
|
},
|
||||||
xAxis: {
|
xAxis: {
|
||||||
@ -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: []
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user