From 9bdbefcfe2fa1d32f0a1c0c9e231e8aa32de6540 Mon Sep 17 00:00:00 2001 From: TongTongStudio Date: Wed, 12 Aug 2026 08:28:10 +0800 Subject: [PATCH] =?UTF-8?q?refactor(device):=20=E4=BC=98=E5=8C=96=E8=AE=BE?= =?UTF-8?q?=E5=A4=87=E8=AF=A6=E6=83=85=E5=AD=97=E6=AE=B5=E4=B8=8E=E5=BF=83?= =?UTF-8?q?=E8=B7=B3=E6=95=B0=E6=8D=AE=E5=8A=A0=E8=BD=BD=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增设备最后心跳查询接口及类型定义 - 设备在线状态字段统一更名为 online,移除重复字段 - 设备详情首次加载时从心跳接口获取最后上线时间 - WebSocket 推送在线状态时同步更新屏幕状态和最后心跳时间 - 编辑设备弹窗精简为仅可修改昵称,其余字段改为只读展示 --- src/api/system/device/index.ts | 14 +++++ src/api/system/device/types.ts | 18 +++++- src/components/DeviceSn/DeviceSnDetail.vue | 61 ++++++++++++++++---- src/components/DeviceSn/DeviceSnOverView.vue | 4 +- src/views/devices/sn/config/edit.ts | 57 +----------------- 5 files changed, 84 insertions(+), 70 deletions(-) diff --git a/src/api/system/device/index.ts b/src/api/system/device/index.ts index a3e77bd0..18cb1d89 100644 --- a/src/api/system/device/index.ts +++ b/src/api/system/device/index.ts @@ -5,6 +5,7 @@ import type { DeviceQueryParams, DeviceItem, DeviceOnlineVO, + DeviceHeartbeatVO, DeviceProfileDetail, DeviceProfileForm, DevicePasswordChangeForm, @@ -488,6 +489,19 @@ const DeviceAPI = { }); }, + /** + * 查询设备最后心跳记录(来自 TDengine) + * + * @param sn 设备序列号 + */ + getLastHeartbeat(sn: string) { + return request({ + url: `${DEVICE_BASE_URL}/online/heartbeat/last`, + method: "get", + params: { sn }, + }); + }, + /** * 上传应用图标(全局图标库) * diff --git a/src/api/system/device/types.ts b/src/api/system/device/types.ts index 9a1acca7..4574dcad 100644 --- a/src/api/system/device/types.ts +++ b/src/api/system/device/types.ts @@ -74,6 +74,22 @@ export interface DeviceOnlineVO { screenState?: number; } +/** 设备最后心跳视图对象(来自 TDengine) */ +export interface DeviceHeartbeatVO { + /** 设备序列号 */ + sn?: string; + /** 最后心跳时间 */ + lastTime?: string; + /** 在线状态(1:在线;0:离线) */ + online?: number; + /** 设备 IP */ + ip?: string; + /** 信号强度 */ + rssi?: string; + /** 屏幕是否亮起(1:亮屏;0:熄屏) */ + screenOn?: number; +} + /** 设备表单对象 */ export interface DeviceForm { /** 设备ID */ @@ -87,7 +103,7 @@ export interface DeviceForm { /** 设备手机号 */ snMobile?: string; /** 设备在线状态(1:在线;0:离线) */ - onlineStatus?: number; + online?: number; /** 设备IMEI */ snImei?: string; /** 所属部门 */ diff --git a/src/components/DeviceSn/DeviceSnDetail.vue b/src/components/DeviceSn/DeviceSnDetail.vue index cd0e4692..92b9548d 100644 --- a/src/components/DeviceSn/DeviceSnDetail.vue +++ b/src/components/DeviceSn/DeviceSnDetail.vue @@ -14,15 +14,28 @@ {{ deviceData.onlineStatus == 1 ? "在线" : "离线" }} + + {{ + deviceData.screenState == 1 + ? "亮屏" + : deviceData.screenState === 0 + ? "熄屏" + : "未知" + }} +
序列号: - {{ props.serialno }} + {{ formatText(props.serialno) }}
IMEI: - {{ formatText(deviceData.imei) }} + {{ formatText(deviceData.imei || networkInfo.mobileImei) }}
设备所有者: @@ -50,11 +63,11 @@
最后上线: - {{ formatText(deviceData.lastOnline) }} + {{ formatDate(deviceData.lastOnline) }}
设备编号: - {{ formatText(deviceData.deviceSn) }} + {{ formatText() }}
@@ -491,9 +504,6 @@ {{ formatText(otherInfo.channel) }} - - {{ formatText(otherInfo.deviceId) }} - {{ formatText(otherInfo.pushId) }} @@ -509,9 +519,6 @@ {{ formatText(otherInfo.iccid) }} - - {{ formatText(otherInfo.imsi) }} - {{ formatBool(otherInfo.isCharging) }} @@ -818,6 +825,7 @@ const activeTab = ref("deviceInfo"); const deviceData = reactive({ snName: "", onlineStatus: 0, + screenState: null as number | null, owner: "", department: "", group: "", @@ -947,6 +955,9 @@ function assignTo(reactiveObj: T, data?: object) { } } +/** 首次进入时是否已加载过最后心跳时间 */ +let lastHeartbeatLoaded = false; + /** 加载设备头部信息 */ async function loadDeviceDetail(serialno: string) { try { @@ -954,7 +965,7 @@ async function loadDeviceDetail(serialno: string) { if (response) { Object.assign(deviceData, { snName: response.snName, - onlineStatus: response.onlineStatus, + onlineStatus: response.online, deviceSn: response.serialno || serialno, imei: response.snImei, department: response.department, @@ -971,20 +982,38 @@ async function loadDeviceDetail(serialno: string) { } // 加载完设备基础信息后,单独查询设备在线状态 loadOnlineStatus(serialno); + // 首次进入时从接口获取最后心跳时间作为最后上线时间 + if (!lastHeartbeatLoaded) { + loadLastHeartbeat(serialno); + lastHeartbeatLoaded = true; + } } -/** 查询单台设备在线状态 */ +/** 查询单台设备在线状态(仅更新在线状态和屏幕状态,不更新最后上线时间) */ async function loadOnlineStatus(serialno: string) { try { const onlineInfo = await DeviceAPI.getOnlineStatus(serialno); if (onlineInfo) { deviceData.onlineStatus = onlineInfo.online ?? 0; + deviceData.screenState = onlineInfo.screenState ?? null; } } catch (error) { console.error("查询设备在线状态失败:", error); } } +/** 查询设备最后心跳时间(仅首次进入时调用,作为最后上线时间) */ +async function loadLastHeartbeat(serialno: string) { + try { + const heartbeat = await DeviceAPI.getLastHeartbeat(serialno); + if (heartbeat?.lastTime) { + deviceData.lastOnline = heartbeat.lastTime; + } + } catch (error) { + console.error("查询设备最后心跳时间失败:", error); + } +} + /** 基本信息 */ async function loadBasicInfo(serialno: string) { loading.basic = true; @@ -1017,6 +1046,10 @@ async function loadNetworkInfo(serialno: string) { try { const data = await DeviceAPI.getNetworkInfo(serialno); assignTo(networkInfo, data); + // 同步 IMEI 到头部信息 + if (data?.mobileImei) { + deviceData.imei = data.mobileImei; + } } catch (e) { console.error("加载网络信息失败", e); } finally { @@ -1357,6 +1390,10 @@ function handleDeviceOnlineStatus(data: DeviceOnlineVO) { // 仅当推送的设备 SN 与当前详情页设备匹配时才更新 if (data.serialno === props.serialno) { deviceData.onlineStatus = data.online ?? 0; + deviceData.screenState = data.screenState ?? null; + if (data.lastHeartbeatTime) { + deviceData.lastOnline = data.lastHeartbeatTime; + } } } diff --git a/src/components/DeviceSn/DeviceSnOverView.vue b/src/components/DeviceSn/DeviceSnOverView.vue index 972a723c..aed29700 100644 --- a/src/components/DeviceSn/DeviceSnOverView.vue +++ b/src/components/DeviceSn/DeviceSnOverView.vue @@ -32,8 +32,8 @@ - - {{ detailData.onlineStatus === 1 ? "在线" : "离线" }} + + {{ detailData.online === 1 ? "在线" : "离线" }} diff --git a/src/views/devices/sn/config/edit.ts b/src/views/devices/sn/config/edit.ts index 2b244b8a..80bf40c2 100644 --- a/src/views/devices/sn/config/edit.ts +++ b/src/views/devices/sn/config/edit.ts @@ -5,16 +5,6 @@ import type { IModalConfig } from "@/components/DeviceSn/types"; import { DeviceEnum } from "@/enums/settings"; import { useAppStore } from "@/stores"; -/** 只读输入框配置(展示常用参数,不可编辑) */ -const readonlyInput = (placeholder = "系统自动填充") => ({ - type: "input" as const, - attrs: { - placeholder, - readonly: true, - disabled: true, - }, -}); - const modalConfig: IModalConfig = { permPrefix: "sys:device", component: "drawer", @@ -23,11 +13,10 @@ const modalConfig: IModalConfig = { size: useAppStore().device === DeviceEnum.MOBILE ? "80%" : 500, }, pk: "id", - // 编辑只回传可修改字段,其余保持不变 + // 编辑只回传可修改字段(仅设备昵称) beforeSubmit(data) { return { id: data.id, - serialno: data.serialno, snName: data.snName, status: data.status, } as DeviceForm; @@ -43,14 +32,9 @@ const modalConfig: IModalConfig = { attrs: { placeholder: "设备序列号", readonly: true, + disabled: true, }, }, - { - label: "设备型号", - prop: "snModel", - rules: [{ required: true, message: "设备型号不能为空", trigger: "blur" }], - ...readonlyInput("设备型号"), - }, { label: "设备昵称", prop: "snName", @@ -61,43 +45,6 @@ const modalConfig: IModalConfig = { maxlength: 50, }, }, - { - label: "手机号码", - prop: "snMobile", - rules: [ - { - pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, - message: "请输入正确的手机号码", - trigger: "blur", - }, - ], - ...readonlyInput("绑定手机号"), - }, - { - label: "IMEI", - prop: "snImei", - ...readonlyInput("设备IMEI"), - }, - { - label: "设备类型", - prop: "deviceType", - ...readonlyInput("设备类型"), - }, - { - label: "来源", - prop: "source", - ...readonlyInput("设备来源"), - }, - { - label: "激活时间", - prop: "activationTime", - ...readonlyInput("激活时间"), - }, - { - label: "最后上线", - prop: "lastOnline", - ...readonlyInput("最后上线时间"), - }, { label: "状态", prop: "status",