<template>
|
<div>
|
<el-dialog v-model="isShow"
|
title="物联设备实时数采"
|
width="800"
|
@close="closeModal">
|
<div v-if="loading" v-loading="loading" element-loading-text="加载中..." style="min-height: 200px;"></div>
|
<div v-else-if="!hasDevices" class="empty-state">
|
<el-empty description="暂无绑定的物联设备">
|
<el-button type="primary" @click="closeModal">去绑定</el-button>
|
</el-empty>
|
</div>
|
<div v-else>
|
<div class="device-header">
|
<div class="refresh-switch">
|
<el-switch v-model="autoRefresh" active-text="自动刷新(30s)" />
|
</div>
|
<el-button type="primary" size="small" @click="fetchData" :loading="loading">
|
<el-icon><Refresh /></el-icon>刷新
|
</el-button>
|
</div>
|
<div class="devices-container">
|
<el-card v-for="device in deviceData.devices" :key="device.deviceId" class="device-card">
|
<template #header>
|
<div class="device-header-info">
|
<div class="device-title">
|
<span class="device-name">{{ device.deviceName }}</span>
|
<el-tag size="small" type="info">{{ device.deviceModel }}</el-tag>
|
</div>
|
<div class="device-status">
|
<span class="status-dot" :class="getStatusClass(device.status)"></span>
|
<span :class="getStatusTextClass(device.status)">{{ getStatusLabel(device.status) }}</span>
|
</div>
|
</div>
|
</template>
|
<div v-if="device.status === '在线'" class="device-data">
|
<el-row :gutter="10">
|
<el-col :span="8" v-if="device.temperature">
|
<div class="data-item">
|
<el-icon class="data-icon"><Sunny /></el-icon>
|
<div class="data-info">
|
<div class="data-label">温度</div>
|
<div class="data-value">{{ device.temperature }}</div>
|
</div>
|
</div>
|
</el-col>
|
<el-col :span="8" v-if="device.humidity">
|
<div class="data-item">
|
<el-icon class="data-icon"><Drizzling /></el-icon>
|
<div class="data-info">
|
<div class="data-label">湿度</div>
|
<div class="data-value">{{ device.humidity }}</div>
|
</div>
|
</div>
|
</el-col>
|
<el-col :span="8" v-if="device.co2">
|
<div class="data-item">
|
<el-icon class="data-icon"><WindPower /></el-icon>
|
<div class="data-info">
|
<div class="data-label">CO2</div>
|
<div class="data-value">{{ device.co2 }}</div>
|
</div>
|
</div>
|
</el-col>
|
<el-col :span="8" v-if="device.light">
|
<div class="data-item">
|
<el-icon class="data-icon"><Sunrise /></el-icon>
|
<div class="data-info">
|
<div class="data-label">光照</div>
|
<div class="data-value">{{ device.light }}</div>
|
</div>
|
</div>
|
</el-col>
|
<el-col :span="8" v-if="device.battery">
|
<div class="data-item">
|
<el-icon class="data-icon"><Lightning /></el-icon>
|
<div class="data-info">
|
<div class="data-label">电量</div>
|
<div class="data-value">{{ device.battery }}</div>
|
</div>
|
</div>
|
</el-col>
|
</el-row>
|
</div>
|
<div v-else class="device-offline">
|
<el-alert :title="device.statusMessage || (device.status === 'error' ? '设备异常' : '设备离线')" :type="device.status === 'error' ? 'error' : 'warning'" :closable="false" show-icon />
|
</div>
|
</el-card>
|
</div>
|
</div>
|
<template #footer>
|
<div class="dialog-footer">
|
<el-button @click="closeModal">关闭</el-button>
|
</div>
|
</template>
|
</el-dialog>
|
</div>
|
</template>
|
|
<script setup>
|
import { ref, computed, onMounted, onUnmounted, watch } from "vue";
|
import { batchGetIotRealtimeData } from "@/api/inventoryManagement/stockInventory.js";
|
import { Refresh, Sunny, Drizzling, WindPower, Sunrise, Lightning } from "@element-plus/icons-vue";
|
|
const props = defineProps({
|
visible: {
|
type: Boolean,
|
required: true,
|
},
|
record: {
|
type: Object,
|
default: () => ({}),
|
},
|
});
|
|
const emit = defineEmits(["update:visible"]);
|
|
const isShow = computed({
|
get() {
|
return props.visible;
|
},
|
set(val) {
|
emit("update:visible", val);
|
},
|
});
|
|
const loading = ref(false);
|
const deviceData = ref({
|
inventoryId: null,
|
iotDeviceIds: "",
|
devices: [],
|
});
|
const autoRefresh = ref(false);
|
let refreshTimer = null;
|
|
const hasDevices = computed(() => {
|
return deviceData.value.devices && deviceData.value.devices.length > 0;
|
});
|
|
const getStatusClass = (status) => {
|
switch (status) {
|
case "在线":
|
return "status-online";
|
case "offline":
|
return "status-offline";
|
case "error":
|
return "status-error";
|
default:
|
return "status-offline";
|
}
|
};
|
|
const getStatusTextClass = (status) => {
|
switch (status) {
|
case "在线":
|
return "text-online";
|
case "offline":
|
return "text-offline";
|
case "error":
|
return "text-error";
|
default:
|
return "text-offline";
|
}
|
};
|
|
const getStatusLabel = (status) => {
|
switch (status) {
|
case "在线":
|
return "在线";
|
case "offline":
|
return "离线";
|
case "error":
|
return "异常";
|
default:
|
return "离线";
|
}
|
};
|
|
const fetchData = async () => {
|
const deviceIdsStr = props.record?.warehouse;
|
console.log("fetchData called, warehouse:", deviceIdsStr);
|
if (!deviceIdsStr) {
|
console.warn("warehouse is empty, skip fetch");
|
deviceData.value = { inventoryId: null, iotDeviceIds: "", devices: [] };
|
return;
|
}
|
loading.value = true;
|
try {
|
const deviceIds = deviceIdsStr.split(",").map(id => id.trim()).filter(id => id);
|
const res = await batchGetIotRealtimeData(deviceIds);
|
|
if (res.code === 200 && res.data) {
|
deviceData.value = res.data;
|
} else {
|
deviceData.value = { inventoryId: null, iotDeviceIds: deviceIdsStr, devices: [] };
|
}
|
} catch (error) {
|
console.error("获取物联设备数据失败:", error);
|
} finally {
|
loading.value = false;
|
}
|
};
|
|
const closeModal = () => {
|
autoRefresh.value = false;
|
isShow.value = false;
|
};
|
|
// 监听弹窗显示状态,每次打开时获取数据
|
watch(() => props.visible, (val) => {
|
console.log("visible changed:", val, "record:", props.record);
|
if (val) {
|
fetchData();
|
} else {
|
// 关闭时停止自动刷新
|
autoRefresh.value = false;
|
}
|
}, { immediate: true });
|
|
// 自动刷新
|
watch(autoRefresh, (val) => {
|
if (val) {
|
refreshTimer = setInterval(() => {
|
fetchData();
|
}, 30000);
|
} else {
|
if (refreshTimer) {
|
clearInterval(refreshTimer);
|
refreshTimer = null;
|
}
|
}
|
});
|
|
onUnmounted(() => {
|
if (refreshTimer) {
|
clearInterval(refreshTimer);
|
}
|
});
|
</script>
|
|
<style scoped>
|
.empty-state {
|
padding: 40px 0;
|
}
|
|
.device-header {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
margin-bottom: 20px;
|
}
|
|
.refresh-switch {
|
display: flex;
|
align-items: center;
|
}
|
|
.devices-container {
|
display: flex;
|
flex-direction: column;
|
gap: 15px;
|
}
|
|
.device-card {
|
margin-bottom: 10px;
|
}
|
|
.device-header-info {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
}
|
|
.device-title {
|
display: flex;
|
align-items: center;
|
gap: 10px;
|
}
|
|
.device-name {
|
font-weight: bold;
|
font-size: 16px;
|
}
|
|
.device-status {
|
display: flex;
|
align-items: center;
|
gap: 5px;
|
}
|
|
.status-dot {
|
width: 8px;
|
height: 8px;
|
border-radius: 50%;
|
display: inline-block;
|
}
|
|
.status-online {
|
background-color: #67c23a;
|
}
|
|
.status-offline {
|
background-color: #909399;
|
}
|
|
.status-error {
|
background-color: #f56c6c;
|
}
|
|
.text-online {
|
color: #67c23a;
|
}
|
|
.text-offline {
|
color: #909399;
|
}
|
|
.text-error {
|
color: #f56c6c;
|
}
|
|
.device-data {
|
padding: 10px 0;
|
}
|
|
.data-item {
|
display: flex;
|
align-items: center;
|
padding: 10px;
|
background-color: #f5f7fa;
|
border-radius: 4px;
|
margin-bottom: 10px;
|
}
|
|
.data-icon {
|
font-size: 24px;
|
color: #409eff;
|
margin-right: 10px;
|
}
|
|
.data-info {
|
flex: 1;
|
}
|
|
.data-label {
|
font-size: 12px;
|
color: #909399;
|
margin-bottom: 2px;
|
}
|
|
.data-value {
|
font-size: 16px;
|
font-weight: bold;
|
color: #303133;
|
}
|
|
.device-offline {
|
padding: 20px 0;
|
}
|
</style>
|