diff --git a/src/main/java/com/yixiong/iot/model/DeviceItem.java b/src/main/java/com/yixiong/iot/model/DeviceItem.java index d2111ea..5cea386 100644 --- a/src/main/java/com/yixiong/iot/model/DeviceItem.java +++ b/src/main/java/com/yixiong/iot/model/DeviceItem.java @@ -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 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 imsiOld; + private List 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; } diff --git a/src/main/resources/templates/index.ftl b/src/main/resources/templates/index.ftl index db9c8c5..be3bbe7 100644 --- a/src/main/resources/templates/index.ftl +++ b/src/main/resources/templates/index.ftl @@ -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 '-'; + const t = new Date(val); + if (isNaN(t.getTime())) return '-'; + // 取本地日期零点,按日历日计算天数差,避免时分秒干扰 + 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 '' + text + ''; + } 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 = '
暂无数据
'; return; } const statusTag = s => s===1?'在线':s===0?'离线':'未激活'; - el.innerHTML = '
' - + list.map(d => '' + el.innerHTML = '
设备 ID设备名称状态创建时间激活时间最后在线操作
' + + list.map(d => '' + '' - + '' + + '' + '' + '' + '' + '' + + '' + '
设备 ID设备名称状态创建时间激活时间最后在线最近操作
' + (d.did||'-') + '' + (d.name||'-') + '' + (d.name||'-') + (isTestDevice(d)?'测试':'') + '' + statusTag(d.status) + '' + fmtDate(d.create_time) + '' + fmtDate(d.activate_time) + '' + fmtDate(d.last_time) + '' + fmtRel(d.last_time) + '' + '' + '' @@ -330,14 +357,35 @@ if (res.code !== 0) { document.getElementById('modal-detail-body').innerHTML = '
错误:' + res.msg + '
'; return; } const d = res.data || {}; const statusTag = s => s===1?'在线':s===0?'离线':'未激活'; + // 视联网协议 / 上报网络类型枚举映射 + const viotProtocol = {1:'GB28181', 2:'千里眼'}; + const viotDeviceType = {1:'IPC', 2:'NVR'}; + const upNetwork = {0:'非蜂窝', 1:'移动', 2:'电信', 3:'联通', 4:'广电'}; document.getElementById('modal-detail-body').innerHTML = '
' + [ ['设备 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]) => '
'+k+''+(v??'-')+'
').join('') + '
'; }).catch(e => { document.getElementById('modal-detail-body').innerHTML = '
请求失败:' + e.message + '
'; }); }