fix: 修正设备详情字段映射并增强设备列表展示
- DeviceItem: 修复 lon/lat 注解大小写导致经纬度恒为空的问题 - DeviceItem: 补全视联网(viot_*)与机卡协同上报(up_*)等缺失字段 - index.ftl: 设备详情弹窗补全字段展示,枚举值友好映射 - index.ftl: 设备列表重点标记测试设备(整行高亮+测试徽章) - index.ftl: 新增"最近"列,按最后在线时间显示今天/昨天/前天/N天前 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -3,23 +3,27 @@ package com.yixiong.iot.model;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 设备列表中单个设备的数据结构
|
||||
* 设备详情 / 设备列表项的数据结构
|
||||
* 字段对应 OneNET 平台 /device/detail 接口返回的 data 节点,
|
||||
* 同时兼容 /device/list 列表项(列表仅返回部分字段,缺失字段为 null)。
|
||||
*/
|
||||
@Data
|
||||
public class DeviceItem {
|
||||
|
||||
/** 设备 ID */
|
||||
/** 设备 ID(平台返回 int/string,统一用 String 接收,避免前端 Long 精度丢失) */
|
||||
private String did;
|
||||
|
||||
/** 产品 ID */
|
||||
private String pid;
|
||||
|
||||
/** 接入协议:2-MQTT,4-LwM2M */
|
||||
/** 接入协议,取值见 OneNET 接入协议取值说明表(如 51) */
|
||||
@JsonProperty("access_pt")
|
||||
private Integer accessPt;
|
||||
|
||||
/** 数据协议 */
|
||||
/** 数据协议,取值见 OneNET 数据协议取值说明表 */
|
||||
@JsonProperty("data_pt")
|
||||
private Integer dataPt;
|
||||
|
||||
@@ -29,6 +33,10 @@ public class DeviceItem {
|
||||
/** 设备描述 */
|
||||
private String desc;
|
||||
|
||||
/** 设备鉴权信息 */
|
||||
@JsonProperty("auth_info")
|
||||
private String authInfo;
|
||||
|
||||
/**
|
||||
* 设备状态
|
||||
* 0-离线;1-在线;2-未激活
|
||||
@@ -79,14 +87,20 @@ public class DeviceItem {
|
||||
@JsonProperty("enable_status")
|
||||
private Boolean enableStatus;
|
||||
|
||||
/** 经度 */
|
||||
@JsonProperty("Lon")
|
||||
/** 设备标签列表 */
|
||||
private List<String> tags;
|
||||
|
||||
/** 经度(注意:平台返回小写 lon,勿用大写注解) */
|
||||
@JsonProperty("lon")
|
||||
private String lon;
|
||||
|
||||
/** 纬度 */
|
||||
@JsonProperty("Lat")
|
||||
/** 纬度(注意:平台返回小写 lat,勿用大写注解) */
|
||||
@JsonProperty("lat")
|
||||
private String lat;
|
||||
|
||||
/** 设备芯片信息 */
|
||||
private Integer chip;
|
||||
|
||||
/** 设备资源自动订阅是否启用 */
|
||||
private Boolean obsv;
|
||||
|
||||
@@ -100,17 +114,106 @@ public class DeviceItem {
|
||||
|
||||
/** 设备 IMSI 历史变更记录 */
|
||||
@JsonProperty("imsi_old")
|
||||
private java.util.List<String> imsiOld;
|
||||
private List<String> imsiOld;
|
||||
|
||||
/** 设备 IMSI 最近一次修改时间 */
|
||||
@JsonProperty("imsi_mt")
|
||||
private String imsiMt;
|
||||
|
||||
/** 移动视联网设备唯一标识 */
|
||||
// ===== 视联网设备字段 =====
|
||||
|
||||
/** 视频设备编码,视联网设备返回 */
|
||||
@JsonProperty("viot_device_sn")
|
||||
private String viotDeviceSn;
|
||||
|
||||
/** 移动视联网设备接入协议 */
|
||||
/**
|
||||
* 视频设备接入协议,视联网设备返回
|
||||
* 1-GB28181 协议;2-千里眼协议
|
||||
*/
|
||||
@JsonProperty("viot_protocol")
|
||||
private Integer viotProtocol;
|
||||
|
||||
/**
|
||||
* 视频设备类型,视联网设备返回
|
||||
* 1-IPC;2-NVR
|
||||
*/
|
||||
@JsonProperty("viot_device_type")
|
||||
private Integer viotDeviceType;
|
||||
|
||||
// ===== 视联网国标协议(GB28181)设备字段 =====
|
||||
|
||||
/** SIP 服务器 ID,国标摄像头设备返回 */
|
||||
@JsonProperty("viot_sip_server_id")
|
||||
private String viotSipServerId;
|
||||
|
||||
/** SIP 服务器域,国标摄像头设备返回 */
|
||||
@JsonProperty("viot_sip_server_domain")
|
||||
private String viotSipServerDomain;
|
||||
|
||||
/** SIP 服务器 IP,国标摄像头设备返回 */
|
||||
@JsonProperty("viot_sip_server_ip")
|
||||
private String viotSipServerIp;
|
||||
|
||||
/** SIP 服务器端口,国标摄像头设备返回 */
|
||||
@JsonProperty("viot_sip_server_port")
|
||||
private String viotSipServerPort;
|
||||
|
||||
/** 登录用户名,国标摄像头设备返回 */
|
||||
@JsonProperty("viot_login_name")
|
||||
private String viotLoginName;
|
||||
|
||||
/** 登录密码,国标摄像头设备返回 */
|
||||
@JsonProperty("viot_login_pwd")
|
||||
private String viotLoginPwd;
|
||||
|
||||
// ===== 视联网千里眼协议设备字段 =====
|
||||
|
||||
/** 设备 modelId,如:cmiot_E4702_002 */
|
||||
@JsonProperty("viot_module_id")
|
||||
private String viotModuleId;
|
||||
|
||||
/** 软件版本,如:2.860.20250512 */
|
||||
@JsonProperty("viot_camera_app_version")
|
||||
private String viotCameraAppVersion;
|
||||
|
||||
/** 固件版本,如:2.860.20250512 */
|
||||
@JsonProperty("viot_firmware_version")
|
||||
private String viotFirmwareVersion;
|
||||
|
||||
/** 千里眼设备 imei(cmei),如:119094672043670 */
|
||||
@JsonProperty("viot_imei")
|
||||
private String viotImei;
|
||||
|
||||
// ===== 机卡协同设备上报字段(设备上报后返回) =====
|
||||
|
||||
/** 设备上报的 iccid,如:898604310000000000 */
|
||||
@JsonProperty("up_iccid")
|
||||
private String upIccid;
|
||||
|
||||
/** 设备上报的 msisdn,如:+8613800000000 */
|
||||
@JsonProperty("up_msisdn")
|
||||
private String upMsisdn;
|
||||
|
||||
/** 设备上报的 sn */
|
||||
@JsonProperty("up_sn")
|
||||
private String upSn;
|
||||
|
||||
/** 设备上报的 imei,如:119094672043670 */
|
||||
@JsonProperty("up_imei")
|
||||
private String upImei;
|
||||
|
||||
/** 设备上报的 imsi,如:460010000000000 */
|
||||
@JsonProperty("up_imsi")
|
||||
private String upImsi;
|
||||
|
||||
/** 设备上报的 mac,如:08:00:27:01:02:03 */
|
||||
@JsonProperty("up_mac")
|
||||
private String upMac;
|
||||
|
||||
/**
|
||||
* 设备上报的网络类型
|
||||
* 0-非蜂窝网;1-移动;2-电信;3-联通;4-广电
|
||||
*/
|
||||
@JsonProperty("up_network")
|
||||
private Integer upNetwork;
|
||||
}
|
||||
|
||||
@@ -49,6 +49,13 @@
|
||||
.tag-info { background: #e6f4ff; color: #1677ff; }
|
||||
.tag-warn { background: #fff7e6; color: #d46b08; }
|
||||
.tag-error { background: #fff0f0; color: #d32f2f; }
|
||||
/* 测试设备标记 */
|
||||
.tag-test { background: #fff7e6; color: #d46b08; border: 1px solid #ffd591; margin-left: 6px; }
|
||||
.row-test td { background: #fffbe6 !important; }
|
||||
/* 最后在线相对时间配色:今天绿色、昨天/前天橙色、更久灰色 */
|
||||
.rel-today { color: #389e0d; font-weight: 600; }
|
||||
.rel-recent { color: #d46b08; }
|
||||
.rel-old { color: #999; }
|
||||
.pagination { display: flex; align-items: center; gap: 8px; margin-top: 14px; font-size: 13px; color: #666; }
|
||||
.page-btn { padding: 5px 12px; border: 1px solid #ddd; border-radius: 4px; background: #fff; cursor: pointer; font-size: 13px; }
|
||||
.page-btn:hover:not(:disabled) { border-color: #1677ff; color: #1677ff; }
|
||||
@@ -251,6 +258,22 @@
|
||||
if (isNaN(d.getTime())) return val;
|
||||
return d.toLocaleString('zh-CN', {hour12: false});
|
||||
}
|
||||
// 将 ISO/时间戳格式化为相对今天的描述:今天 / 昨天 / 前天 / N天前
|
||||
// 返回带颜色 class 的 span,今天绿色,昨天/前天橙色,更久灰色
|
||||
function fmtRel(val) {
|
||||
if (!val) return '<span class="rel-old">-</span>';
|
||||
const t = new Date(val);
|
||||
if (isNaN(t.getTime())) return '<span class="rel-old">-</span>';
|
||||
// 取本地日期零点,按日历日计算天数差,避免时分秒干扰
|
||||
const startOfDay = d => new Date(d.getFullYear(), d.getMonth(), d.getDate()).getTime();
|
||||
const diff = Math.floor((startOfDay(new Date()) - startOfDay(t)) / 86400000);
|
||||
let text, cls;
|
||||
if (diff <= 0) { text = '今天'; cls = 'rel-today'; } // 今天或异常未来时间兜底
|
||||
else if (diff === 1) { text = '昨天'; cls = 'rel-recent'; }
|
||||
else if (diff === 2) { text = '前天'; cls = 'rel-recent'; }
|
||||
else { text = diff + '天前'; cls = 'rel-old'; }
|
||||
return '<span class="' + cls + '">' + text + '</span>';
|
||||
}
|
||||
function switchTab(name) {
|
||||
document.querySelectorAll('.tab-btn').forEach((b, i) => b.classList.toggle('active', ['product','device'][i] === name));
|
||||
document.querySelectorAll('.tab-panel').forEach(p => p.classList.remove('active'));
|
||||
@@ -288,6 +311,9 @@
|
||||
|
||||
// ===== 设备列表 =====
|
||||
let _curDeviceName = '', _curDeviceLimit = 10;
|
||||
// 重点关注的测试设备(按 imei 或 设备名称 命中),列表中整行高亮 + 名称后加测试徽章
|
||||
const TEST_DEVICES = new Set(['863079075181776', '863079075146761']);
|
||||
const isTestDevice = d => TEST_DEVICES.has(d.imei) || TEST_DEVICES.has(d.name);
|
||||
function queryDeviceList(offset) {
|
||||
_curDeviceName = document.getElementById('device-name-input').value.trim();
|
||||
_curDeviceLimit = parseInt(document.getElementById('device-limit-select').value);
|
||||
@@ -300,14 +326,15 @@
|
||||
const list = res.data.list || [];
|
||||
if (!list.length) { el.innerHTML = '<div class="tip">暂无数据</div>'; return; }
|
||||
const statusTag = s => s===1?'<span class="tag tag-online">在线</span>':s===0?'<span class="tag tag-offline">离线</span>':'<span class="tag tag-inactive">未激活</span>';
|
||||
el.innerHTML = '<div class="table-wrap"><table><thead><tr><th>设备 ID</th><th>设备名称</th><th>状态</th><th>创建时间</th><th>激活时间</th><th>最后在线</th><th>操作</th></tr></thead><tbody>'
|
||||
+ list.map(d => '<tr>'
|
||||
el.innerHTML = '<div class="table-wrap"><table><thead><tr><th>设备 ID</th><th>设备名称</th><th>状态</th><th>创建时间</th><th>激活时间</th><th>最后在线</th><th>最近</th><th>操作</th></tr></thead><tbody>'
|
||||
+ list.map(d => '<tr class="' + (isTestDevice(d)?'row-test':'') + '">'
|
||||
+ '<td>' + (d.did||'-') + '</td>'
|
||||
+ '<td>' + (d.name||'-') + '</td>'
|
||||
+ '<td>' + (d.name||'-') + (isTestDevice(d)?'<span class="tag tag-test">测试</span>':'') + '</td>'
|
||||
+ '<td>' + statusTag(d.status) + '</td>'
|
||||
+ '<td>' + fmtDate(d.create_time) + '</td>'
|
||||
+ '<td>' + fmtDate(d.activate_time) + '</td>'
|
||||
+ '<td>' + fmtDate(d.last_time) + '</td>'
|
||||
+ '<td>' + fmtRel(d.last_time) + '</td>'
|
||||
+ '<td><button class="btn-sm btn-detail" onclick="showDeviceDetail(\'' + d.name + '\')">详情</button>'
|
||||
+ '<button class="btn-sm btn-data" onclick="showDatapoints(\'' + (d.name||'') + '\',\'' + (d.did||'') + '\')">数据点</button>'
|
||||
+ '<button class="btn-sm btn-event" onclick="showEventFromList(\'' + (d.name||'') + '\',\'' + (d.did||'') + '\')">事件记录</button>'
|
||||
@@ -330,14 +357,35 @@
|
||||
if (res.code !== 0) { document.getElementById('modal-detail-body').innerHTML = '<div class="err">错误:' + res.msg + '</div>'; return; }
|
||||
const d = res.data || {};
|
||||
const statusTag = s => s===1?'<span class="tag tag-online">在线</span>':s===0?'<span class="tag tag-offline">离线</span>':'<span class="tag tag-inactive">未激活</span>';
|
||||
// 视联网协议 / 上报网络类型枚举映射
|
||||
const viotProtocol = {1:'GB28181', 2:'千里眼'};
|
||||
const viotDeviceType = {1:'IPC', 2:'NVR'};
|
||||
const upNetwork = {0:'非蜂窝', 1:'移动', 2:'电信', 3:'联通', 4:'广电'};
|
||||
document.getElementById('modal-detail-body').innerHTML = '<div class="detail-grid">' + [
|
||||
['设备 ID', d.did], ['设备名称', d.name], ['描述', d.desc||'-'],
|
||||
['产品 ID', d.pid], ['状态', statusTag(d.status)],
|
||||
['接入协议', d.access_pt], ['数据协议', d.data_pt],
|
||||
['鉴权信息', d.auth_info||'-'], ['标签', (d.tags&&d.tags.length)?d.tags.join(','):'-'],
|
||||
['创建时间', fmtDate(d.create_time)], ['激活时间', fmtDate(d.activate_time)],
|
||||
['最后在线', fmtDate(d.last_time)], ['IMEI', d.imei||'-'],
|
||||
['IMSI', d.imsi||'-'], ['经度', d.lon||'-'], ['纬度', d.lat||'-'],
|
||||
['分组 ID', d.group_id||'-'], ['鉴权 Key', d.sec_key||'-'],
|
||||
['启用状态', d.enable_status?'启用':'禁用'], ['自动订阅', d.obsv?'是':'否'],
|
||||
['订阅状态', d.obsv_st?'是':'否'], ['私密性', d.private?'是':'否'],
|
||||
// 视联网设备字段
|
||||
['视频编码', d.viot_device_sn||'-'],
|
||||
['视频协议', d.viot_protocol!=null?(viotProtocol[d.viot_protocol]??d.viot_protocol):'-'],
|
||||
['视频类型', d.viot_device_type!=null?(viotDeviceType[d.viot_device_type]??d.viot_device_type):'-'],
|
||||
['SIP 服务器 ID', d.viot_sip_server_id||'-'], ['SIP 域', d.viot_sip_server_domain||'-'],
|
||||
['SIP IP', d.viot_sip_server_ip||'-'], ['SIP 端口', d.viot_sip_server_port||'-'],
|
||||
['登录用户名', d.viot_login_name||'-'], ['登录密码', d.viot_login_pwd||'-'],
|
||||
['ModelId', d.viot_module_id||'-'], ['软件版本', d.viot_camera_app_version||'-'],
|
||||
['固件版本', d.viot_firmware_version||'-'], ['千里眼 IMEI', d.viot_imei||'-'],
|
||||
// 机卡协同上报字段
|
||||
['上报 ICCID', d.up_iccid||'-'], ['上报 MSISDN', d.up_msisdn||'-'],
|
||||
['上报 SN', d.up_sn||'-'], ['上报 IMEI', d.up_imei||'-'],
|
||||
['上报 IMSI', d.up_imsi||'-'], ['上报 MAC', d.up_mac||'-'],
|
||||
['上报网络', d.up_network!=null?(upNetwork[d.up_network]??d.up_network):'-'],
|
||||
].map(([k,v]) => '<div class="detail-item"><span class="detail-label">'+k+'</span><span class="detail-value">'+(v??'-')+'</span></div>').join('') + '</div>';
|
||||
}).catch(e => { document.getElementById('modal-detail-body').innerHTML = '<div class="err">请求失败:' + e.message + '</div>'; });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user