仓库传感器详情页修改
首页修改
This commit is contained in:
@@ -57,6 +57,24 @@
|
||||
<p>{{ item.locationName }}</p>
|
||||
<h2>{{ item.typeName }}</h2>
|
||||
<strong>{{ item.summary }}</strong>
|
||||
<div v-if="item.type === 'socket'" class="socket-actions">
|
||||
<button
|
||||
type="button"
|
||||
class="control-btn control-on"
|
||||
:disabled="!!controlLoadingMap[item.cardKey]"
|
||||
@click="handleSocketControl(item, 'on')"
|
||||
>
|
||||
{{ controlLoadingMap[item.cardKey] ? '发送中...' : '开启排风' }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="control-btn control-off"
|
||||
:disabled="!!controlLoadingMap[item.cardKey]"
|
||||
@click="handleSocketControl(item, 'off')"
|
||||
>
|
||||
{{ controlLoadingMap[item.cardKey] ? '发送中...' : '关闭排风' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -87,8 +105,11 @@
|
||||
<script setup name="WarehouseDashboard">
|
||||
import { computed, onBeforeUnmount, onMounted, reactive, ref } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { listMqttDevice } from '@/api/worn/mqttDevice'
|
||||
import { listMqttData } from '@/api/worn/mqttData'
|
||||
import { listMqttEvent } from '@/api/worn/mqttEvent'
|
||||
import { controlSocket } from '@/api/worn/socket'
|
||||
import { closeWs, connectWs } from '@/utils/ws'
|
||||
|
||||
const route = useRoute()
|
||||
@@ -97,6 +118,7 @@ const router = useRouter()
|
||||
const loading = ref(false)
|
||||
const devices = ref([])
|
||||
const latestDataMap = reactive({})
|
||||
const controlLoadingMap = reactive({})
|
||||
const ENV_CARD_TYPES = ['tempHum', 'toxicGas', 'hydrogenSulfide']
|
||||
|
||||
const deptId = computed(() => route.query.deptId)
|
||||
@@ -140,16 +162,42 @@ function loadLatestData() {
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
return listMqttData({
|
||||
pageNum: 1,
|
||||
pageSize: 1,
|
||||
deviceId: device.id
|
||||
}).then((res) => {
|
||||
const row = (res.rows || [])[0]
|
||||
if (row) {
|
||||
latestDataMap[getDeviceKey(device)] = normalizePayload(row)
|
||||
}
|
||||
}).catch(() => {})
|
||||
const requestList = [
|
||||
listMqttData({
|
||||
pageNum: 1,
|
||||
pageSize: 1,
|
||||
deviceId: device.id
|
||||
}).then((res) => {
|
||||
const row = (res.rows || [])[0]
|
||||
if (row) {
|
||||
latestDataMap[getDeviceKey(device)] = normalizePayload({
|
||||
...(latestDataMap[getDeviceKey(device)] || {}),
|
||||
...row
|
||||
})
|
||||
}
|
||||
}).catch(() => {})
|
||||
]
|
||||
|
||||
if (isStateDevice(device)) {
|
||||
requestList.push(
|
||||
listMqttEvent({
|
||||
pageNum: 1,
|
||||
pageSize: 1,
|
||||
deviceId: device.id
|
||||
}).then((res) => {
|
||||
const row = (res.rows || [])[0]
|
||||
if (row) {
|
||||
latestDataMap[getDeviceKey(device)] = normalizePayload({
|
||||
...(latestDataMap[getDeviceKey(device)] || {}),
|
||||
...row,
|
||||
...deriveStateFromEvent(row)
|
||||
})
|
||||
}
|
||||
}).catch(() => {})
|
||||
)
|
||||
}
|
||||
|
||||
return Promise.all(requestList)
|
||||
})
|
||||
|
||||
return Promise.all(tasks)
|
||||
@@ -160,20 +208,80 @@ function handleWsMessage(data) {
|
||||
return
|
||||
}
|
||||
|
||||
const key = getDeviceKey(data)
|
||||
latestDataMap[key] = normalizePayload(data)
|
||||
|
||||
const index = devices.value.findIndex((device) => {
|
||||
return String(device.id || '') === String(data.deviceId || '') ||
|
||||
String(device.devEui || '').toLowerCase() === String(data.devEUI || data.devEui || '').toLowerCase()
|
||||
})
|
||||
|
||||
if (index > -1) {
|
||||
const device = devices.value[index]
|
||||
const key = getDeviceKey(device)
|
||||
const patchedData = patchRealtimeState(latestDataMap[key] || {}, data)
|
||||
latestDataMap[key] = normalizePayload({
|
||||
...(latestDataMap[key] || {}),
|
||||
...patchedData
|
||||
})
|
||||
|
||||
devices.value[index] = {
|
||||
...devices.value[index],
|
||||
...device,
|
||||
status: '0',
|
||||
deviceName: data.deviceName || devices.value[index].deviceName
|
||||
deviceName: data.deviceName || device.deviceName
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const key = getDeviceKey(data)
|
||||
const patchedData = patchRealtimeState(latestDataMap[key] || {}, data)
|
||||
latestDataMap[key] = normalizePayload({
|
||||
...(latestDataMap[key] || {}),
|
||||
...patchedData
|
||||
})
|
||||
}
|
||||
|
||||
function patchRealtimeState(previous, incoming) {
|
||||
const next = { ...incoming }
|
||||
const waterStatus = deriveWaterStatus({ ...previous, ...incoming })
|
||||
if (waterStatus !== undefined) {
|
||||
next.waterStatus = waterStatus
|
||||
next.water_status = waterStatus
|
||||
}
|
||||
|
||||
const doorStatus = deriveDoorStatus({ ...previous, ...incoming })
|
||||
if (doorStatus !== undefined) {
|
||||
next.doorStatus = doorStatus
|
||||
next.door_status = doorStatus
|
||||
}
|
||||
|
||||
const socketStatus = deriveSocketStatus({ ...previous, ...incoming })
|
||||
if (socketStatus !== undefined) {
|
||||
next.switchStatus = socketStatus
|
||||
next.socket_status = socketStatus
|
||||
next.socketStatus = socketStatus
|
||||
}
|
||||
|
||||
return next
|
||||
}
|
||||
|
||||
async function handleSocketControl(item, action) {
|
||||
const deviceId = item.id || item.deviceId
|
||||
const devEui = item.devEui || item.devEUI || item.dev_eui
|
||||
if (!devEui) {
|
||||
ElMessage.error('未找到设备 DevEUI,无法下发指令')
|
||||
return
|
||||
}
|
||||
|
||||
const loadingKey = item.cardKey || String(deviceId)
|
||||
if (controlLoadingMap[loadingKey]) {
|
||||
return
|
||||
}
|
||||
|
||||
controlLoadingMap[loadingKey] = true
|
||||
try {
|
||||
const status = action === 'on' ? 1 : 0
|
||||
await controlSocket({ devEui, deviceId, status })
|
||||
ElMessage.success(action === 'on' ? '开启排风指令已发送' : '关闭排风指令已发送')
|
||||
} finally {
|
||||
controlLoadingMap[loadingKey] = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,11 +325,27 @@ function getDeviceKey(device) {
|
||||
|
||||
function normalizePayload(row) {
|
||||
const json = parseJson(row.dataJson) || parseJson(row.payload) || {}
|
||||
const nestedData = typeof row.data === 'object'
|
||||
? row.data
|
||||
: (typeof row.data === 'string' ? parseJson(row.data) : null) || {}
|
||||
const inferredWaterStatus = deriveWaterStatus({
|
||||
...json,
|
||||
...nestedData,
|
||||
...row
|
||||
})
|
||||
|
||||
return {
|
||||
...json,
|
||||
...nestedData,
|
||||
...row,
|
||||
devEUI: row.devEUI || row.devEui || json.devEUI || json.devEui,
|
||||
deviceName: row.deviceName || json.deviceName
|
||||
deviceName: row.deviceName || json.deviceName || nestedData.deviceName,
|
||||
waterStatus: row.waterStatus ?? row.water_status ?? nestedData.waterStatus ?? nestedData.water_status ?? json.waterStatus ?? json.water_status ?? inferredWaterStatus,
|
||||
water_status: row.water_status ?? row.waterStatus ?? nestedData.water_status ?? nestedData.waterStatus ?? json.water_status ?? json.waterStatus ?? inferredWaterStatus,
|
||||
socket_status: row.socket_status ?? row.socketStatus ?? nestedData.socket_status ?? nestedData.socketStatus ?? json.socket_status ?? json.socketStatus,
|
||||
switchStatus: row.switchStatus ?? row.socket_status ?? row.socketStatus ?? nestedData.switchStatus ?? nestedData.socket_status ?? nestedData.socketStatus ?? json.switchStatus ?? json.socket_status ?? json.socketStatus,
|
||||
doorStatus: row.doorStatus ?? row.door_status ?? nestedData.doorStatus ?? nestedData.door_status ?? json.doorStatus ?? json.door_status,
|
||||
tamperStatus: row.tamperStatus ?? row.tamper_status ?? nestedData.tamperStatus ?? nestedData.tamper_status ?? json.tamperStatus ?? json.tamper_status
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,6 +361,98 @@ function parseJson(value) {
|
||||
}
|
||||
}
|
||||
|
||||
function deriveStateFromEvent(row) {
|
||||
const eventType = String(row.eventType || row.event_type || row.event || '').trim().toLowerCase()
|
||||
const eventDesc = String(row.eventDesc || row.event_desc || row.statusDesc || row.status_desc || '').trim().toLowerCase()
|
||||
const normalized = {
|
||||
eventType: row.eventType || row.event_type,
|
||||
eventDesc: row.eventDesc || row.event_desc,
|
||||
statusDesc: row.statusDesc || row.status_desc
|
||||
}
|
||||
|
||||
if (eventType === 'door_open' || eventDesc.includes('门已打开')) {
|
||||
normalized.doorStatus = 1
|
||||
} else if (eventType === 'door_close' || eventDesc.includes('门已关闭')) {
|
||||
normalized.doorStatus = 0
|
||||
}
|
||||
|
||||
if (eventType === 'socket_on' || eventDesc.includes('插座通电')) {
|
||||
normalized.socket_status = 1
|
||||
normalized.switchStatus = 1
|
||||
} else if (eventType === 'socket_off' || eventDesc.includes('插座关闭') || eventDesc.includes('插座断电')) {
|
||||
normalized.socket_status = 0
|
||||
normalized.switchStatus = 0
|
||||
}
|
||||
|
||||
const inferredWaterStatus = deriveWaterStatus(row)
|
||||
if (inferredWaterStatus !== undefined) {
|
||||
normalized.waterStatus = inferredWaterStatus
|
||||
normalized.water_status = inferredWaterStatus
|
||||
}
|
||||
|
||||
return normalized
|
||||
}
|
||||
|
||||
function deriveWaterStatus(data) {
|
||||
const eventType = String(data.eventType || data.event_type || data.event || data.status || '').trim().toLowerCase()
|
||||
const eventDesc = String(data.eventDesc || data.event_desc || data.statusDesc || data.status_desc || '').trim().toLowerCase()
|
||||
|
||||
if (eventType === 'alarm' || eventDesc.includes('浸水预警') || eventDesc.includes('水浸预警') || eventDesc.includes('浸水报警') || eventDesc.includes('水浸报警')) {
|
||||
return 1
|
||||
}
|
||||
|
||||
if (eventType === 'recovery' || eventType === 'normal' || eventDesc.includes('水浸正常') || eventDesc.includes('浸水正常')) {
|
||||
return 0
|
||||
}
|
||||
|
||||
const directValue = data.waterStatus ?? data.water_status ?? data.water
|
||||
if (directValue !== undefined && directValue !== null && directValue !== '') {
|
||||
return directValue
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
function deriveDoorStatus(data) {
|
||||
const directValue = data.doorStatus ?? data.door_status
|
||||
if (directValue !== undefined && directValue !== null && directValue !== '') {
|
||||
return directValue
|
||||
}
|
||||
|
||||
const eventType = String(data.eventType || data.event_type || data.event || data.status || '').trim().toLowerCase()
|
||||
const eventDesc = String(data.eventDesc || data.event_desc || data.statusDesc || data.status_desc || '').trim().toLowerCase()
|
||||
|
||||
if (eventType === 'door_open' || eventDesc.includes('门已打开')) {
|
||||
return 1
|
||||
}
|
||||
|
||||
if (eventType === 'door_close' || eventDesc.includes('门已关闭')) {
|
||||
return 0
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
function deriveSocketStatus(data) {
|
||||
const directValue = data.switchStatus ?? data.socket_status ?? data.socketStatus
|
||||
if (directValue !== undefined && directValue !== null && directValue !== '') {
|
||||
return directValue
|
||||
}
|
||||
|
||||
const eventType = String(data.eventType || data.event_type || data.event || data.status || '').trim().toLowerCase()
|
||||
const eventDesc = String(data.eventDesc || data.event_desc || data.statusDesc || data.status_desc || '').trim().toLowerCase()
|
||||
|
||||
if (eventType === 'socket_on' || eventDesc.includes('插座通电')) {
|
||||
return 1
|
||||
}
|
||||
|
||||
if (eventType === 'socket_off' || eventDesc.includes('插座关闭') || eventDesc.includes('插座断电')) {
|
||||
return 0
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
function getSensorType(device, data) {
|
||||
const raw = `${device.deviceType || ''} ${device.deviceName || ''} ${data.type || ''}`.toLowerCase()
|
||||
if (raw.includes('smoke')) return 'smoke'
|
||||
@@ -247,6 +463,11 @@ function getSensorType(device, data) {
|
||||
return 'sensor'
|
||||
}
|
||||
|
||||
function isStateDevice(device) {
|
||||
const raw = `${device.deviceType || ''} ${device.deviceName || ''}`.toLowerCase()
|
||||
return raw.includes('door') || raw.includes('socket')
|
||||
}
|
||||
|
||||
function getSensorIcon(type) {
|
||||
const iconMap = {
|
||||
smoke: '烟',
|
||||
@@ -295,11 +516,11 @@ function getSensorSummary(data, type) {
|
||||
}
|
||||
|
||||
if (type === 'socket') {
|
||||
return `插座状态:${translateSwitch(data.switchStatus ?? data.socket_status)}`
|
||||
return `插座状态:${translateSwitch(data.switchStatus ?? data.socket_status ?? data.socketStatus ?? data.status)}`
|
||||
}
|
||||
|
||||
if (type === 'door') {
|
||||
return `门禁状态:${translateSwitch(data.doorStatus)}`
|
||||
return `门禁状态:${translateSwitch(data.doorStatus ?? data.door_status ?? data.status)}`
|
||||
}
|
||||
|
||||
if (type === 'env') {
|
||||
@@ -368,8 +589,12 @@ function buildMetrics(data, type) {
|
||||
metrics.unshift({ label: '水浸状态', value: translateNormalAlarm(data.waterStatus ?? data.water_status ?? data.water) })
|
||||
}
|
||||
|
||||
if (type === 'socket' && (data.switchStatus !== undefined || data.socket_status !== undefined)) {
|
||||
metrics.unshift({ label: '插座状态', value: translateSwitch(data.switchStatus ?? data.socket_status) })
|
||||
if (type === 'socket' && (data.switchStatus !== undefined || data.socket_status !== undefined || data.socketStatus !== undefined || data.status !== undefined)) {
|
||||
metrics.unshift({ label: '插座状态', value: translateSwitch(data.switchStatus ?? data.socket_status ?? data.socketStatus ?? data.status) })
|
||||
}
|
||||
|
||||
if (type === 'door' && (data.doorStatus !== undefined || data.door_status !== undefined || data.status !== undefined)) {
|
||||
metrics.unshift({ label: '门禁状态', value: translateSwitch(data.doorStatus ?? data.door_status ?? data.status) })
|
||||
}
|
||||
|
||||
return metrics.slice(0, 6)
|
||||
@@ -403,9 +628,9 @@ function translateSmokeStatus(value) {
|
||||
}
|
||||
|
||||
function translateSwitch(value) {
|
||||
const normalized = String(value).toLowerCase()
|
||||
if (normalized === '0' || normalized === 'off' || normalized === 'false') return '关闭'
|
||||
if (normalized === '1' || normalized === 'on' || normalized === 'true') return '开启'
|
||||
const normalized = String(value ?? '').trim().toLowerCase()
|
||||
if (normalized === '0' || normalized === 'off' || normalized === 'false' || normalized === 'close' || normalized === 'closed') return '关闭'
|
||||
if (normalized === '1' || normalized === 'on' || normalized === 'true' || normalized === 'open' || normalized === 'opened') return '开启'
|
||||
return value || '--'
|
||||
}
|
||||
|
||||
@@ -777,6 +1002,47 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
}
|
||||
|
||||
.socket-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
.control-btn {
|
||||
min-width: 94px;
|
||||
padding: 9px 14px;
|
||||
font-size: 13px;
|
||||
font-weight: 800;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 999px;
|
||||
transition: transform 0.18s ease, box-shadow 0.18s ease, opacity 0.18s ease;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.68;
|
||||
}
|
||||
}
|
||||
|
||||
.control-on {
|
||||
color: #065f46;
|
||||
background: linear-gradient(135deg, #dcfce7, #f0fdf4);
|
||||
border-color: #86efac;
|
||||
box-shadow: 0 10px 18px rgba(34, 197, 94, 0.12);
|
||||
}
|
||||
|
||||
.control-off {
|
||||
color: #9a3412;
|
||||
background: linear-gradient(135deg, #ffedd5, #fff7ed);
|
||||
border-color: #fdba74;
|
||||
box-shadow: 0 10px 18px rgba(249, 115, 22, 0.12);
|
||||
}
|
||||
|
||||
.metric-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
Reference in New Issue
Block a user