仓库传感器详情页修改
首页修改
This commit is contained in:
@@ -15,3 +15,19 @@ export function controlSocket(data) {
|
||||
data: payload
|
||||
})
|
||||
}
|
||||
|
||||
// 智慧照明开关远程控制
|
||||
export function controlLightSwitch(data) {
|
||||
const payload = {
|
||||
devEui: data.devEui,
|
||||
channel: data.channel,
|
||||
status: data.status
|
||||
}
|
||||
|
||||
return request({
|
||||
url: '/worn/socket/switch',
|
||||
method: 'post',
|
||||
params: payload,
|
||||
data: payload
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@ import { getToken } from '@/utils/auth'
|
||||
let ws = null
|
||||
let reconnectTimer = null
|
||||
let manualClose = false
|
||||
let currentOnMessage = null
|
||||
let retryCount = 0
|
||||
const subscribers = new Set()
|
||||
|
||||
const WS_PATH = '/ws'
|
||||
const MAX_RECONNECT_DELAY = 30000
|
||||
@@ -37,7 +37,10 @@ export function connectWs(onMessage) {
|
||||
return
|
||||
}
|
||||
|
||||
currentOnMessage = onMessage
|
||||
if (typeof onMessage === 'function') {
|
||||
subscribers.add(onMessage)
|
||||
}
|
||||
|
||||
manualClose = false
|
||||
clearReconnectTimer()
|
||||
|
||||
@@ -61,17 +64,23 @@ export function connectWs(onMessage) {
|
||||
// Keep plain text messages as-is.
|
||||
}
|
||||
|
||||
currentOnMessage && currentOnMessage(data, event)
|
||||
subscribers.forEach((handler) => {
|
||||
try {
|
||||
handler(data, event)
|
||||
} catch (error) {
|
||||
console.error('[WebSocket] subscriber error', error)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
ws.onclose = () => {
|
||||
console.log('[WebSocket] closed')
|
||||
ws = null
|
||||
|
||||
if (!manualClose) {
|
||||
if (!manualClose && subscribers.size > 0) {
|
||||
reconnectTimer = setTimeout(() => {
|
||||
console.log('[WebSocket] reconnecting')
|
||||
connectWs(currentOnMessage)
|
||||
connectWs()
|
||||
}, getReconnectDelay())
|
||||
}
|
||||
}
|
||||
@@ -81,21 +90,42 @@ export function connectWs(onMessage) {
|
||||
}
|
||||
}
|
||||
|
||||
export function closeWs() {
|
||||
export function closeWs(onMessage) {
|
||||
if (typeof onMessage === 'function') {
|
||||
subscribers.delete(onMessage)
|
||||
} else {
|
||||
subscribers.clear()
|
||||
}
|
||||
|
||||
if (subscribers.size > 0) {
|
||||
return
|
||||
}
|
||||
|
||||
manualClose = true
|
||||
retryCount = 0
|
||||
clearReconnectTimer()
|
||||
if (ws) {
|
||||
ws.close()
|
||||
ws = null
|
||||
}
|
||||
}
|
||||
|
||||
export function reconnectWs(onMessage) {
|
||||
if (typeof onMessage === 'function') {
|
||||
subscribers.add(onMessage)
|
||||
}
|
||||
|
||||
manualClose = false
|
||||
retryCount = 0
|
||||
clearReconnectTimer()
|
||||
|
||||
if (ws) {
|
||||
ws.close()
|
||||
ws = null
|
||||
}
|
||||
}
|
||||
|
||||
export function reconnectWs(onMessage = currentOnMessage) {
|
||||
closeWs()
|
||||
manualClose = false
|
||||
connectWs(onMessage)
|
||||
connectWs()
|
||||
}
|
||||
|
||||
export function sendWs(data) {
|
||||
|
||||
@@ -391,7 +391,7 @@ function normalizeEvent(data) {
|
||||
const now = new Date()
|
||||
const type = data?.type || 'unknown'
|
||||
const level = getEventLevel(data)
|
||||
const deviceName = data?.deviceName || `设备 ${data?.deviceId || ''}`.trim()
|
||||
const deviceName = getRealtimeDeviceLabel(data)
|
||||
const deptName = data?.deptName || `部门 ${data?.deptId || ''}`.trim()
|
||||
const desc = translateEventValue(data?.eventDesc || data?.statusDesc || data?.event || getTypeName(type))
|
||||
|
||||
@@ -405,6 +405,57 @@ function normalizeEvent(data) {
|
||||
}
|
||||
}
|
||||
|
||||
function getRealtimeDeviceLabel(data) {
|
||||
const rawDeviceName = String(data?.deviceName || '').trim()
|
||||
const remark = String(data?.remark || data?.deviceRemark || data?.deviceAlias || '').trim()
|
||||
const typeLabel = getRealtimeDeviceTypeName(data)
|
||||
|
||||
if (remark) {
|
||||
if (rawDeviceName && rawDeviceName !== remark) {
|
||||
return `${remark}(${rawDeviceName})`
|
||||
}
|
||||
return remark
|
||||
}
|
||||
|
||||
if (typeLabel) {
|
||||
if (rawDeviceName) {
|
||||
return `${typeLabel}(${rawDeviceName})`
|
||||
}
|
||||
return typeLabel
|
||||
}
|
||||
|
||||
return rawDeviceName || `设备 ${data?.deviceId || ''}`.trim()
|
||||
}
|
||||
|
||||
function getRealtimeDeviceTypeName(data) {
|
||||
const rawType = String(data?.deviceType || data?.type || '').trim().toLowerCase()
|
||||
const rawName = String(data?.deviceName || '').trim().toLowerCase()
|
||||
|
||||
if (rawType === 'switch' || rawName.includes('light-switch') || rawName.includes('light switch')) {
|
||||
return '智慧照明开关'
|
||||
}
|
||||
if (rawName.includes('ac-socket') || rawName.includes('ac socket') || rawName.includes('空调')) {
|
||||
return '智能空调插座'
|
||||
}
|
||||
if (rawType === 'socket' || rawName.includes('socket')) {
|
||||
return '智能排风'
|
||||
}
|
||||
if (rawType === 'smoke' || rawName.includes('smoke')) {
|
||||
return '烟雾传感器'
|
||||
}
|
||||
if (rawType === 'water' || rawName.includes('water')) {
|
||||
return '水浸传感器'
|
||||
}
|
||||
if (rawType === 'door' || rawName.includes('door')) {
|
||||
return '门禁传感器'
|
||||
}
|
||||
if (rawType === 'env' || rawName.includes('env')) {
|
||||
return '环境传感器'
|
||||
}
|
||||
|
||||
return ''
|
||||
}
|
||||
|
||||
function isDuplicateRealtimeEvent(event) {
|
||||
const now = Date.now()
|
||||
const lastTime = recentEventKeys.get(event.fingerprint)
|
||||
@@ -436,7 +487,10 @@ function buildEventFingerprint(data) {
|
||||
concentration: data?.concentration,
|
||||
water: data?.water,
|
||||
doorStatus: data?.doorStatus,
|
||||
tamperStatus: data?.tamperStatus
|
||||
tamperStatus: data?.tamperStatus,
|
||||
switch1: data?.switch1 ?? data?.switch_1,
|
||||
switch2: data?.switch2 ?? data?.switch_2,
|
||||
switch3: data?.switch3 ?? data?.switch_3
|
||||
})
|
||||
}
|
||||
|
||||
@@ -540,7 +594,13 @@ function buildExtraMetrics(data, knownKeys) {
|
||||
'deptName',
|
||||
'eventDesc',
|
||||
'statusDesc',
|
||||
'time'
|
||||
'time',
|
||||
'switch_1_change',
|
||||
'switch1_change',
|
||||
'switch_2_change',
|
||||
'switch2_change',
|
||||
'switch_3_change',
|
||||
'switch3_change'
|
||||
])
|
||||
|
||||
return Object.keys(data || {})
|
||||
@@ -559,6 +619,9 @@ function formatExtraMetricValue(key, value) {
|
||||
if (key === 'level') {
|
||||
return translateLevelValue(value)
|
||||
}
|
||||
if (['switch_1', 'switch_2', 'switch_3', 'switch1', 'switch2', 'switch3'].includes(key)) {
|
||||
return translateSwitchShort(value)
|
||||
}
|
||||
return String(value)
|
||||
}
|
||||
|
||||
@@ -607,11 +670,28 @@ function getExtraMetricLabel(key) {
|
||||
const labelMap = {
|
||||
event: '事件',
|
||||
eventType: '事件类型',
|
||||
level: '等级'
|
||||
level: '等级',
|
||||
switch_1: '开关1',
|
||||
switch_2: '开关2',
|
||||
switch_3: '开关3',
|
||||
switch1: '开关1',
|
||||
switch2: '开关2',
|
||||
switch3: '开关3'
|
||||
}
|
||||
return labelMap[key] || key
|
||||
}
|
||||
|
||||
function translateSwitchShort(value) {
|
||||
const normalized = String(value ?? '').trim().toLowerCase()
|
||||
if (normalized === '1' || normalized === 'on' || normalized === 'true' || normalized === 'open' || normalized === 'opened') {
|
||||
return '开'
|
||||
}
|
||||
if (normalized === '0' || normalized === 'off' || normalized === 'false' || normalized === 'close' || normalized === 'closed') {
|
||||
return '关'
|
||||
}
|
||||
return String(value ?? '--')
|
||||
}
|
||||
|
||||
function getEventLevel(data) {
|
||||
if (data?.type === 'alarm' || data?.eventType === 'alarm' || data?.event === 'alarm') {
|
||||
return 'alarm'
|
||||
@@ -680,7 +760,7 @@ onBeforeUnmount(() => {
|
||||
clearTimeout(tickerTimer)
|
||||
tickerTimer = null
|
||||
}
|
||||
closeWs()
|
||||
closeWs(handleWsMessage)
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -57,6 +57,7 @@
|
||||
<el-table-column label="设备唯一标识" align="center" prop="devEui" />
|
||||
<el-table-column label="设备名称" align="center" prop="deviceName" />
|
||||
<el-table-column label="设备类型" align="center" prop="deviceType" />
|
||||
<el-table-column label="上报周期(分钟)" align="center" prop="reportIntervalMinute" />
|
||||
<el-table-column label="所属部门" align="center" prop="deptId">
|
||||
<template #default="scope">
|
||||
{{ getDictLabel(scope.row.deptId, departmentList, { name: 'deptName', value: 'deptId' }) }}
|
||||
@@ -109,6 +110,21 @@
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="上报周期(分钟)" prop="reportIntervalMinute">
|
||||
<el-input-number
|
||||
v-model="form.reportIntervalMinute"
|
||||
:min="1"
|
||||
:max="1440"
|
||||
controls-position="right"
|
||||
placeholder="请输入上报周期"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12"></el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="备注" prop="remark">
|
||||
@@ -157,6 +173,7 @@ const data = reactive({
|
||||
devEui: null,
|
||||
deviceName: null,
|
||||
deviceType: null,
|
||||
reportIntervalMinute: null,
|
||||
deptId: null,
|
||||
status: null
|
||||
},
|
||||
@@ -202,6 +219,7 @@ function reset() {
|
||||
devEui: null,
|
||||
deviceName: null,
|
||||
deviceType: null,
|
||||
reportIntervalMinute: null,
|
||||
deptId: null,
|
||||
status: null,
|
||||
remark: null,
|
||||
|
||||
@@ -57,14 +57,14 @@
|
||||
<p>{{ item.locationName }}</p>
|
||||
<h2>{{ item.typeName }}</h2>
|
||||
<strong>{{ item.summary }}</strong>
|
||||
<div v-if="item.type === 'socket'" class="socket-actions">
|
||||
<div v-if="isSocketControlType(item.type)" class="socket-actions">
|
||||
<button
|
||||
type="button"
|
||||
class="control-btn control-on"
|
||||
:disabled="!!controlLoadingMap[item.cardKey]"
|
||||
@click="handleSocketControl(item, 'on')"
|
||||
>
|
||||
{{ controlLoadingMap[item.cardKey] ? '发送中...' : '开启排风' }}
|
||||
{{ getSocketActionText(item.type, 'on', !!controlLoadingMap[item.cardKey]) }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@@ -72,9 +72,34 @@
|
||||
:disabled="!!controlLoadingMap[item.cardKey]"
|
||||
@click="handleSocketControl(item, 'off')"
|
||||
>
|
||||
{{ controlLoadingMap[item.cardKey] ? '发送中...' : '关闭排风' }}
|
||||
{{ getSocketActionText(item.type, 'off', !!controlLoadingMap[item.cardKey]) }}
|
||||
</button>
|
||||
</div>
|
||||
<div v-else-if="item.type === 'switch'" class="switch-actions">
|
||||
<div
|
||||
v-for="channel in [1, 2, 3]"
|
||||
:key="`${item.cardKey}-channel-${channel}`"
|
||||
class="switch-action-row"
|
||||
>
|
||||
<span class="switch-channel-label">开关{{ channel }}</span>
|
||||
<button
|
||||
type="button"
|
||||
class="control-btn control-on"
|
||||
:disabled="!!controlLoadingMap[`${item.cardKey}-channel-${channel}`]"
|
||||
@click="handleLightSwitchControl(item, channel, 'on')"
|
||||
>
|
||||
{{ getLightSwitchActionText(channel, 'on', !!controlLoadingMap[`${item.cardKey}-channel-${channel}`]) }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="control-btn control-off"
|
||||
:disabled="!!controlLoadingMap[`${item.cardKey}-channel-${channel}`]"
|
||||
@click="handleLightSwitchControl(item, channel, 'off')"
|
||||
>
|
||||
{{ getLightSwitchActionText(channel, 'off', !!controlLoadingMap[`${item.cardKey}-channel-${channel}`]) }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -109,7 +134,7 @@ 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 { controlLightSwitch, controlSocket } from '@/api/worn/socket'
|
||||
import { closeWs, connectWs } from '@/utils/ws'
|
||||
|
||||
const route = useRoute()
|
||||
@@ -208,6 +233,10 @@ function handleWsMessage(data) {
|
||||
return
|
||||
}
|
||||
|
||||
const runtimeStatus = data.deviceStatus !== undefined && data.deviceStatus !== null && data.deviceStatus !== ''
|
||||
? String(data.deviceStatus)
|
||||
: null
|
||||
|
||||
const index = devices.value.findIndex((device) => {
|
||||
return String(device.id || '') === String(data.deviceId || '') ||
|
||||
String(device.devEui || '').toLowerCase() === String(data.devEUI || data.devEui || '').toLowerCase()
|
||||
@@ -224,7 +253,7 @@ function handleWsMessage(data) {
|
||||
|
||||
devices.value[index] = {
|
||||
...device,
|
||||
status: '0',
|
||||
status: runtimeStatus ?? '0',
|
||||
deviceName: data.deviceName || device.deviceName
|
||||
}
|
||||
return
|
||||
@@ -240,6 +269,24 @@ function handleWsMessage(data) {
|
||||
|
||||
function patchRealtimeState(previous, incoming) {
|
||||
const next = { ...incoming }
|
||||
|
||||
const switch1 = incoming.switch_1 ?? incoming.switch1
|
||||
const switch2 = incoming.switch_2 ?? incoming.switch2
|
||||
const switch3 = incoming.switch_3 ?? incoming.switch3
|
||||
|
||||
if (switch1 !== undefined && switch1 !== null && switch1 !== '') {
|
||||
next.switch_1 = switch1
|
||||
next.switch1 = switch1
|
||||
}
|
||||
if (switch2 !== undefined && switch2 !== null && switch2 !== '') {
|
||||
next.switch_2 = switch2
|
||||
next.switch2 = switch2
|
||||
}
|
||||
if (switch3 !== undefined && switch3 !== null && switch3 !== '') {
|
||||
next.switch_3 = switch3
|
||||
next.switch3 = switch3
|
||||
}
|
||||
|
||||
const waterStatus = deriveWaterStatus({ ...previous, ...incoming })
|
||||
if (waterStatus !== undefined) {
|
||||
next.waterStatus = waterStatus
|
||||
@@ -279,12 +326,78 @@ async function handleSocketControl(item, action) {
|
||||
try {
|
||||
const status = action === 'on' ? 1 : 0
|
||||
await controlSocket({ devEui, deviceId, status })
|
||||
ElMessage.success(action === 'on' ? '开启排风指令已发送' : '关闭排风指令已发送')
|
||||
syncSocketLocalState(item, status)
|
||||
ElMessage.success(getSocketSuccessMessage(item.type, action))
|
||||
} finally {
|
||||
controlLoadingMap[loadingKey] = false
|
||||
}
|
||||
}
|
||||
|
||||
async function handleLightSwitchControl(item, channel, action) {
|
||||
const devEui = item.devEui || item.devEUI || item.dev_eui
|
||||
if (!devEui) {
|
||||
ElMessage.error('未找到设备 DevEUI,无法下发指令')
|
||||
return
|
||||
}
|
||||
|
||||
const loadingKey = `${item.cardKey}-channel-${channel}`
|
||||
if (controlLoadingMap[loadingKey]) {
|
||||
return
|
||||
}
|
||||
|
||||
controlLoadingMap[loadingKey] = true
|
||||
try {
|
||||
const status = action === 'on' ? 1 : 0
|
||||
await controlLightSwitch({ devEui, channel, status })
|
||||
syncLightSwitchLocalState(item, channel, status)
|
||||
ElMessage.success(`${action === 'on' ? '开启' : '关闭'}${channel}路指令已发送`)
|
||||
} finally {
|
||||
controlLoadingMap[loadingKey] = false
|
||||
}
|
||||
}
|
||||
|
||||
function syncSocketLocalState(item, status) {
|
||||
const key = getDeviceKey(item)
|
||||
if (!key) {
|
||||
return
|
||||
}
|
||||
|
||||
const current = latestDataMap[key] || {}
|
||||
const now = new Date().toISOString()
|
||||
latestDataMap[key] = normalizePayload({
|
||||
...current,
|
||||
deviceId: item.deviceId || item.id,
|
||||
devEui: item.devEui || item.devEUI || item.dev_eui,
|
||||
deviceName: item.deviceName,
|
||||
socket_status: status,
|
||||
socketStatus: status,
|
||||
switchStatus: status,
|
||||
status,
|
||||
gatewayTime: current.gatewayTime || now,
|
||||
createTime: now
|
||||
})
|
||||
}
|
||||
|
||||
function syncLightSwitchLocalState(item, channel, status) {
|
||||
const key = getDeviceKey(item)
|
||||
if (!key) {
|
||||
return
|
||||
}
|
||||
|
||||
const current = latestDataMap[key] || {}
|
||||
const now = new Date().toISOString()
|
||||
latestDataMap[key] = normalizePayload({
|
||||
...current,
|
||||
deviceId: item.deviceId || item.id,
|
||||
devEui: item.devEui || item.devEUI || item.dev_eui,
|
||||
deviceName: item.deviceName,
|
||||
[`switch_${channel}`]: status,
|
||||
[`switch${channel}`]: status,
|
||||
gatewayTime: current.gatewayTime || now,
|
||||
createTime: now
|
||||
})
|
||||
}
|
||||
|
||||
function expandDeviceCards(device) {
|
||||
const data = latestDataMap[getDeviceKey(device)] || {}
|
||||
const type = getSensorType(device, data)
|
||||
@@ -298,7 +411,7 @@ function buildSensorCard(device, displayType) {
|
||||
const data = latestDataMap[getDeviceKey(device)] || {}
|
||||
const sourceType = getSensorType(device, data)
|
||||
const type = displayType || sourceType
|
||||
const metrics = buildMetrics(data, type)
|
||||
const metrics = buildDisplayMetrics(data, type)
|
||||
const reportTime = formatTime(data.createTime || data.gatewayTime || data.time)
|
||||
const isOnline = String(device.status) === '0'
|
||||
|
||||
@@ -307,10 +420,10 @@ function buildSensorCard(device, displayType) {
|
||||
cardKey: `${getDeviceKey(device) || device.id || device.devEui || 'sensor'}-${type}`,
|
||||
sourceType,
|
||||
type,
|
||||
icon: getSensorIcon(type),
|
||||
typeName: getSensorTypeName(type),
|
||||
icon: getDisplayIcon(type),
|
||||
typeName: getDisplayTypeName(type),
|
||||
locationName: getLocationName(device, data),
|
||||
summary: getSensorSummary(data, type),
|
||||
summary: getDisplaySummary(data, type),
|
||||
metrics,
|
||||
reportTime,
|
||||
isOnline,
|
||||
@@ -319,6 +432,72 @@ function buildSensorCard(device, displayType) {
|
||||
}
|
||||
}
|
||||
|
||||
function isSocketControlType(type) {
|
||||
return type === 'socket' || type === 'acSocket'
|
||||
}
|
||||
|
||||
function getSocketSuccessMessage(type, action) {
|
||||
if (type === 'acSocket') {
|
||||
return action === 'on' ? '开启空调指令已发送' : '关闭空调指令已发送'
|
||||
}
|
||||
return action === 'on' ? '开启排风指令已发送' : '关闭排风指令已发送'
|
||||
}
|
||||
|
||||
function getSocketActionText(type, action, loading) {
|
||||
if (loading) {
|
||||
return '发送中...'
|
||||
}
|
||||
|
||||
if (type === 'acSocket') {
|
||||
return action === 'on' ? '开启空调' : '关闭空调'
|
||||
}
|
||||
|
||||
return action === 'on' ? '开启排风' : '关闭排风'
|
||||
}
|
||||
|
||||
function getLightSwitchActionText(channel, action, loading) {
|
||||
if (loading) {
|
||||
return '发送中...'
|
||||
}
|
||||
|
||||
return action === 'on' ? `开${channel}路` : `关${channel}路`
|
||||
}
|
||||
|
||||
function getDisplayIcon(type) {
|
||||
if (type === 'acSocket') {
|
||||
return '空'
|
||||
}
|
||||
return getSensorIcon(type)
|
||||
}
|
||||
|
||||
function getDisplayTypeName(type) {
|
||||
if (type === 'acSocket') {
|
||||
return '智能空调插座'
|
||||
}
|
||||
return getSensorTypeName(type)
|
||||
}
|
||||
|
||||
function getDisplaySummary(data, type) {
|
||||
if (type === 'acSocket') {
|
||||
return `空调状态:${translateSwitch(data.switchStatus ?? data.socket_status ?? data.socketStatus ?? data.status)}`
|
||||
}
|
||||
return getSensorSummary(data, type)
|
||||
}
|
||||
|
||||
function buildDisplayMetrics(data, type) {
|
||||
const metrics = buildMetrics(data, type === 'acSocket' ? 'socket' : type)
|
||||
if (type !== 'acSocket') {
|
||||
return metrics
|
||||
}
|
||||
|
||||
return metrics.map((metric, index) => {
|
||||
if (index === 0 && String(metric.label).includes('插座')) {
|
||||
return { ...metric, label: '空调状态' }
|
||||
}
|
||||
return metric
|
||||
})
|
||||
}
|
||||
|
||||
function getDeviceKey(device) {
|
||||
return device.deviceId || device.id || device.devEUI || device.devEui || device.dev_eui
|
||||
}
|
||||
@@ -344,6 +523,12 @@ function normalizePayload(row) {
|
||||
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,
|
||||
switch_1: row.switch_1 ?? row.switch1 ?? nestedData.switch_1 ?? nestedData.switch1 ?? json.switch_1 ?? json.switch1,
|
||||
switch1: row.switch1 ?? row.switch_1 ?? nestedData.switch1 ?? nestedData.switch_1 ?? json.switch1 ?? json.switch_1,
|
||||
switch_2: row.switch_2 ?? row.switch2 ?? nestedData.switch_2 ?? nestedData.switch2 ?? json.switch_2 ?? json.switch2,
|
||||
switch2: row.switch2 ?? row.switch_2 ?? nestedData.switch2 ?? nestedData.switch_2 ?? json.switch2 ?? json.switch_2,
|
||||
switch_3: row.switch_3 ?? row.switch3 ?? nestedData.switch_3 ?? nestedData.switch3 ?? json.switch_3 ?? json.switch3,
|
||||
switch3: row.switch3 ?? row.switch_3 ?? nestedData.switch3 ?? nestedData.switch_3 ?? json.switch3 ?? json.switch_3,
|
||||
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
|
||||
}
|
||||
@@ -454,10 +639,27 @@ function deriveSocketStatus(data) {
|
||||
}
|
||||
|
||||
function getSensorType(device, data) {
|
||||
const raw = `${device.deviceType || ''} ${device.deviceName || ''} ${data.type || ''}`.toLowerCase()
|
||||
const deviceType = String(device.deviceType || '').trim().toLowerCase()
|
||||
const deviceName = String(device.deviceName || '').trim().toLowerCase()
|
||||
const raw = `${deviceType} ${deviceName} ${data.type || ''}`.toLowerCase()
|
||||
|
||||
if (deviceType === 'smoke') return 'smoke'
|
||||
if (deviceType === 'water') return 'water'
|
||||
if (deviceType === 'door') return 'door'
|
||||
if (deviceType === 'env') return 'env'
|
||||
if (deviceType === 'switch') return 'switch'
|
||||
if (deviceType === 'socket') {
|
||||
if (deviceName.includes('ac-socket') || deviceName.includes('ac socket') || deviceName.includes('空调')) {
|
||||
return 'acSocket'
|
||||
}
|
||||
return 'socket'
|
||||
}
|
||||
|
||||
if (raw.includes('smoke')) return 'smoke'
|
||||
if (raw.includes('water')) return 'water'
|
||||
if (raw.includes('ac-socket') || raw.includes('ac socket') || raw.includes('空调')) return 'acSocket'
|
||||
if (raw.includes('socket')) return 'socket'
|
||||
if (raw.includes('switch')) return 'switch'
|
||||
if (raw.includes('door')) return 'door'
|
||||
if (raw.includes('env') || raw.includes('temperature') || raw.includes('humidity')) return 'env'
|
||||
return 'sensor'
|
||||
@@ -465,7 +667,7 @@ function getSensorType(device, data) {
|
||||
|
||||
function isStateDevice(device) {
|
||||
const raw = `${device.deviceType || ''} ${device.deviceName || ''}`.toLowerCase()
|
||||
return raw.includes('door') || raw.includes('socket')
|
||||
return raw.includes('door') || raw.includes('socket') || raw.includes('switch')
|
||||
}
|
||||
|
||||
function getSensorIcon(type) {
|
||||
@@ -473,6 +675,7 @@ function getSensorIcon(type) {
|
||||
smoke: '烟',
|
||||
water: '水',
|
||||
socket: '电',
|
||||
switch: '灯',
|
||||
door: '门',
|
||||
env: '温',
|
||||
tempHum: '温',
|
||||
@@ -488,6 +691,7 @@ function getSensorTypeName(type) {
|
||||
smoke: '烟雾传感器',
|
||||
water: '水浸传感器',
|
||||
socket: '智能排风',
|
||||
switch: '智慧照明开关',
|
||||
door: '门禁状态',
|
||||
env: '环境传感器',
|
||||
tempHum: '温湿度传感器',
|
||||
@@ -519,6 +723,20 @@ function getSensorSummary(data, type) {
|
||||
return `插座状态:${translateSwitch(data.switchStatus ?? data.socket_status ?? data.socketStatus ?? data.status)}`
|
||||
}
|
||||
|
||||
if (type === 'switch') {
|
||||
const channelParts = [
|
||||
data.switch_1 !== undefined && data.switch_1 !== null && data.switch_1 !== '' ? `1路${translateSwitchShort(data.switch_1)}` : null,
|
||||
data.switch_2 !== undefined && data.switch_2 !== null && data.switch_2 !== '' ? `2路${translateSwitchShort(data.switch_2)}` : null,
|
||||
data.switch_3 !== undefined && data.switch_3 !== null && data.switch_3 !== '' ? `3路${translateSwitchShort(data.switch_3)}` : null
|
||||
].filter(Boolean)
|
||||
|
||||
if (channelParts.length) {
|
||||
return `开关状态:${channelParts.join(' / ')}`
|
||||
}
|
||||
|
||||
return `开关状态:${translateSwitch(data.switchStatus ?? data.switch_status ?? data.socket_status ?? data.socketStatus ?? data.status)}`
|
||||
}
|
||||
|
||||
if (type === 'door') {
|
||||
return `门禁状态:${translateSwitch(data.doorStatus ?? data.door_status ?? data.status)}`
|
||||
}
|
||||
@@ -593,6 +811,16 @@ function buildMetrics(data, type) {
|
||||
metrics.unshift({ label: '插座状态', value: translateSwitch(data.switchStatus ?? data.socket_status ?? data.socketStatus ?? data.status) })
|
||||
}
|
||||
|
||||
if (type === 'switch') {
|
||||
const switchMetrics = [
|
||||
data.switch_1 !== undefined && data.switch_1 !== null && data.switch_1 !== '' ? { label: '开关1', value: translateSwitchShort(data.switch_1) } : null,
|
||||
data.switch_2 !== undefined && data.switch_2 !== null && data.switch_2 !== '' ? { label: '开关2', value: translateSwitchShort(data.switch_2) } : null,
|
||||
data.switch_3 !== undefined && data.switch_3 !== null && data.switch_3 !== '' ? { label: '开关3', value: translateSwitchShort(data.switch_3) } : null
|
||||
].filter(Boolean)
|
||||
|
||||
return [...switchMetrics, ...metrics].slice(0, 6)
|
||||
}
|
||||
|
||||
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) })
|
||||
}
|
||||
@@ -634,6 +862,13 @@ function translateSwitch(value) {
|
||||
return value || '--'
|
||||
}
|
||||
|
||||
function translateSwitchShort(value) {
|
||||
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 || '--'
|
||||
}
|
||||
|
||||
function formatTime(value) {
|
||||
if (!value) {
|
||||
return ''
|
||||
@@ -658,7 +893,7 @@ onMounted(() => {
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
closeWs()
|
||||
closeWs(handleWsMessage)
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -896,7 +1131,9 @@ onBeforeUnmount(() => {
|
||||
|
||||
&.smoke { color: #ea580c; background: linear-gradient(135deg, #ffedd5, #fff7ed); border-color: #fed7aa; }
|
||||
&.water { color: #0284c7; background: linear-gradient(135deg, #e0f2fe, #f0f9ff); border-color: #bae6fd; }
|
||||
&.acSocket { color: #2563eb; background: linear-gradient(135deg, #dbeafe, #eff6ff); border-color: #bfdbfe; }
|
||||
&.socket { color: #16a34a; background: linear-gradient(135deg, #dcfce7, #f0fdf4); border-color: #bbf7d0; }
|
||||
&.switch { color: #ca8a04; background: linear-gradient(135deg, #fef3c7, #fffbeb); border-color: #fcd34d; }
|
||||
&.door { color: #475569; background: linear-gradient(135deg, #e2e8f0, #f8fafc); border-color: #cbd5e1; }
|
||||
&.env { color: #0d9488; background: linear-gradient(135deg, #ccfbf1, #f0fdfa); border-color: #99f6e4; }
|
||||
&.tempHum { color: #0d9488; background: linear-gradient(135deg, #ccfbf1, #f0fdfa); border-color: #99f6e4; }
|
||||
@@ -1008,6 +1245,25 @@ onBeforeUnmount(() => {
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
.switch-actions {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
.switch-action-row {
|
||||
display: grid;
|
||||
grid-template-columns: 48px repeat(2, minmax(0, 1fr));
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.switch-channel-label {
|
||||
font-size: 12px;
|
||||
font-weight: 800;
|
||||
color: #0f766e;
|
||||
}
|
||||
|
||||
.control-btn {
|
||||
min-width: 94px;
|
||||
padding: 9px 14px;
|
||||
|
||||
Reference in New Issue
Block a user