refactor(device): 优化设备详情字段与心跳数据加载逻辑
- 新增设备最后心跳查询接口及类型定义 - 设备在线状态字段统一更名为 online,移除重复字段 - 设备详情首次加载时从心跳接口获取最后上线时间 - WebSocket 推送在线状态时同步更新屏幕状态和最后心跳时间 - 编辑设备弹窗精简为仅可修改昵称,其余字段改为只读展示
This commit is contained in:
@@ -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<any, DeviceHeartbeatVO>({
|
||||
url: `${DEVICE_BASE_URL}/online/heartbeat/last`,
|
||||
method: "get",
|
||||
params: { sn },
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 上传应用图标(全局图标库)
|
||||
*
|
||||
|
||||
@@ -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;
|
||||
/** 所属部门 */
|
||||
|
||||
@@ -14,15 +14,28 @@
|
||||
<el-tag :type="deviceData.onlineStatus == 1 ? 'success' : 'danger'" size="small">
|
||||
{{ deviceData.onlineStatus == 1 ? "在线" : "离线" }}
|
||||
</el-tag>
|
||||
<el-tag
|
||||
v-if="deviceData.onlineStatus == 1"
|
||||
:type="deviceData.screenState == 1 ? 'warning' : 'info'"
|
||||
size="small"
|
||||
>
|
||||
{{
|
||||
deviceData.screenState == 1
|
||||
? "亮屏"
|
||||
: deviceData.screenState === 0
|
||||
? "熄屏"
|
||||
: "未知"
|
||||
}}
|
||||
</el-tag>
|
||||
</div>
|
||||
<div class="info-grid">
|
||||
<div class="info-item">
|
||||
<span class="label">序列号:</span>
|
||||
<span class="value">{{ props.serialno }}</span>
|
||||
<span class="value">{{ formatText(props.serialno) }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="label">IMEI:</span>
|
||||
<span class="value">{{ formatText(deviceData.imei) }}</span>
|
||||
<span class="value">{{ formatText(deviceData.imei || networkInfo.mobileImei) }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="label">设备所有者:</span>
|
||||
@@ -50,11 +63,11 @@
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="label">最后上线:</span>
|
||||
<span class="value">{{ formatText(deviceData.lastOnline) }}</span>
|
||||
<span class="value">{{ formatDate(deviceData.lastOnline) }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="label">设备编号:</span>
|
||||
<span class="value">{{ formatText(deviceData.deviceSn) }}</span>
|
||||
<span class="value">{{ formatText() }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -491,9 +504,6 @@
|
||||
<el-descriptions-item label="渠道">
|
||||
{{ formatText(otherInfo.channel) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="设备 ID">
|
||||
{{ formatText(otherInfo.deviceId) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="极光推送ID">
|
||||
{{ formatText(otherInfo.pushId) }}
|
||||
</el-descriptions-item>
|
||||
@@ -509,9 +519,6 @@
|
||||
<el-descriptions-item label="ICCID">
|
||||
{{ formatText(otherInfo.iccid) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="IMSI">
|
||||
{{ formatText(otherInfo.imsi) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="充电中">
|
||||
{{ formatBool(otherInfo.isCharging) }}
|
||||
</el-descriptions-item>
|
||||
@@ -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<T extends object>(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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,8 +32,8 @@
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="在线状态">
|
||||
<el-tag :type="detailData.onlineStatus === 1 ? 'success' : 'info'" effect="dark" round>
|
||||
{{ detailData.onlineStatus === 1 ? "在线" : "离线" }}
|
||||
<el-tag :type="detailData.online === 1 ? 'success' : 'info'" effect="dark" round>
|
||||
{{ detailData.online === 1 ? "在线" : "离线" }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
@@ -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<DeviceForm> = {
|
||||
permPrefix: "sys:device",
|
||||
component: "drawer",
|
||||
@@ -23,11 +13,10 @@ const modalConfig: IModalConfig<DeviceForm> = {
|
||||
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<DeviceForm> = {
|
||||
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<DeviceForm> = {
|
||||
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",
|
||||
|
||||
Reference in New Issue
Block a user