diff --git a/settings.xml b/settings.xml index 621edeb..6b72727 100644 --- a/settings.xml +++ b/settings.xml @@ -13,3 +13,4 @@ + diff --git a/src/main/java/com/iot/transport/jt808/entity/request/BatchLocationPack.java b/src/main/java/com/iot/transport/jt808/entity/request/BatchLocationPack.java index 31cce6a..beda34d 100644 --- a/src/main/java/com/iot/transport/jt808/entity/request/BatchLocationPack.java +++ b/src/main/java/com/iot/transport/jt808/entity/request/BatchLocationPack.java @@ -50,3 +50,4 @@ public class BatchLocationPack extends DataPack { } } + diff --git a/src/main/java/com/iot/transport/jt808/entity/request/ButtonEventPack.java b/src/main/java/com/iot/transport/jt808/entity/request/ButtonEventPack.java index 0e86c30..9fbd005 100644 --- a/src/main/java/com/iot/transport/jt808/entity/request/ButtonEventPack.java +++ b/src/main/java/com/iot/transport/jt808/entity/request/ButtonEventPack.java @@ -42,3 +42,4 @@ public class ButtonEventPack extends DataPack { } } + diff --git a/src/main/java/com/iot/transport/jt808/service/handler/terminal/BatchLocationHandler.java b/src/main/java/com/iot/transport/jt808/service/handler/terminal/BatchLocationHandler.java index f517b98..0ea804a 100644 --- a/src/main/java/com/iot/transport/jt808/service/handler/terminal/BatchLocationHandler.java +++ b/src/main/java/com/iot/transport/jt808/service/handler/terminal/BatchLocationHandler.java @@ -49,3 +49,4 @@ public class BatchLocationHandler extends MessageHandler { } } + diff --git a/src/main/java/com/iot/transport/jt808/service/handler/terminal/ButtonEventHandler.java b/src/main/java/com/iot/transport/jt808/service/handler/terminal/ButtonEventHandler.java index 0b3c32c..0790c78 100644 --- a/src/main/java/com/iot/transport/jt808/service/handler/terminal/ButtonEventHandler.java +++ b/src/main/java/com/iot/transport/jt808/service/handler/terminal/ButtonEventHandler.java @@ -43,3 +43,4 @@ public class ButtonEventHandler extends MessageHandler { } } + diff --git a/src/main/resources/static/index.html b/src/main/resources/static/index.html index 006ef2c..662fb24 100644 --- a/src/main/resources/static/index.html +++ b/src/main/resources/static/index.html @@ -143,9 +143,6 @@ -
@@ -166,9 +163,26 @@
- + +
+
指令调试 (8300/8201)
+
+ + +
+
+ +
+
@@ -200,21 +214,7 @@
-
- - -
-
- - -
- +
@@ -350,24 +350,42 @@
-
-
{{ counter.id }}
- {{ formatTime(counter.lastUpdate) }} +
+
+
{{ counter.id }}
+ + RSSI: + {{ counter.RSSI }} dBm + +
+
+ {{ formatTime(counter.lastUpdate) }} + v{{ counter.version }} +
-
+ +
-
{{ counter.inCount }}
- In +
{{ counter.inCount }}
+ 今日进入
-
{{ counter.outCount }}
- Out +
{{ counter.outCount }}
+ 今日离开
-
{{ counter.inCount - counter.outCount }}
- Stay +
{{ counter.inCount - counter.outCount }}
+ 当前滞留
+ + +
+ {{ formatCounterTime(counter.latestData.time) }} + + Rx:{{counter.latestData.rxBat}}% / Tx:{{counter.latestData.txBat}}% + +
@@ -494,10 +512,42 @@ } } - // 2. 处理计数器数据 (识别 type=counter) - else if (data.type === 'counter' && data.id) { - this.counters[data.id] = { - ...data, + // 2. 处理计数器数据 (识别 type=counter OR 含有 uuid 字段的复杂JSON) + else if ((data.type === 'counter' && data.id) || (data.uuid && data.data && Array.isArray(data.data))) { + // 统一 ID 字段 + const id = data.id || data.uuid; + + // 尝试解析复杂 JSON 结构 + let totalIn = data.inCount || 0; + let totalOut = data.outCount || 0; + let latestData = null; + + if (data.data && Array.isArray(data.data)) { + // 累加 data 数组中的流量,或者取最新一条(取决于业务逻辑,这里假设是累积值或者我们显示最新状态) + // 通常计数器上报的是当次时间段内的增量或者当前的累计值。 + // 这里假设我们需要展示最新的累积值,或者自行累加。 + // 简化处理:显示最新一条数据的 in/out,或者如果后端没做累加,前端简单累加 + // 由于示例数据看起来像是一段时间的记录,我们取最后一条作为"最新状态" + + // 找出时间最近的一条 + const sortedData = [...data.data].sort((a, b) => b.time.localeCompare(a.time)); + if (sortedData.length > 0) { + latestData = sortedData[0]; + // 如果 JSON 里的 in/out 是累计值,直接使用;如果是增量,需要累加。 + // 根据示例 "in": 22,看起来像累计值。 + totalIn = latestData.in || 0; // Fix: use || 0 to avoid undefined + totalOut = latestData.out || 0; // Fix: use || 0 to avoid undefined + } + } + + this.counters[id] = { + id: id, + // 优先使用外层字段,没有则使用解析出的字段 + inCount: totalIn, + outCount: totalOut, + RSSI: data.RSSI, + version: data.version, + latestData: latestData, lastUpdate: now }; } @@ -526,8 +576,15 @@ headers: {'Content-Type': 'application/json'}, body: JSON.stringify(payload) }); + + if (!res.ok) { + throw new Error(`HTTP ${res.status}`); + } + const result = await res.json(); - if (result.code !== 200) alert('Error: ' + result.message); + if (result.code !== 200) { + alert('Error: ' + (result.message || result.msg || '未知错误')); + } } catch (e) { alert('发送失败: ' + e.message); } @@ -597,7 +654,7 @@ }); const result = await res.json(); if (result.code === 200) alert('指令已发送'); - else alert('失败: ' + result.message); + else alert('失败: ' + (result.message || '未知错误')); } else if (this.commandForm.apiType === 'text') { // 调用 /api/v1/device/command/text @@ -613,7 +670,7 @@ }); const result = await res.json(); if (result.code === 200) alert('指令已发送'); - else alert('失败: ' + result.message); + else alert('失败: ' + (result.message || '未知错误')); } else { // 通用指令接口 /api/v1/device/command/send @@ -626,7 +683,7 @@ }); const result = await res.json(); if (result.code === 200) alert('指令已发送'); - else alert('失败: ' + result.message); + else alert('失败: ' + (result.message || '未知错误')); } } catch (e) { alert('发送异常: ' + e.message); @@ -636,7 +693,11 @@ // 辅助函数 formatTime(date) { if (!date) return ''; - return new Date(date).toLocaleTimeString(); + try { + return new Date(date).toLocaleTimeString(); + } catch (e) { + return ''; + } }, getBatteryColor(level) { if (level > 60) return 'text-success'; @@ -677,6 +738,18 @@ if (rssi >= -85) return '#fd7e14'; // Orange return '#dc3545'; // Red }, + getRssiColorClass(rssi) { + if (!rssi) return 'text-muted'; + if (rssi >= -60) return 'text-success'; + if (rssi >= -75) return 'text-warning'; + if (rssi >= -85) return 'text-warning'; // Orange-ish + return 'text-danger'; + }, + formatCounterTime(str) { + // 20250501093000 -> 09:30:00 + if (!str || str.length < 14) return str; + return str.substring(8, 10) + ':' + str.substring(10, 12) + ':' + str.substring(12, 14); + }, // 发送 TTS async sendTTS(phone) { const content = this.ttsInputs[phone]; diff --git a/target/classes/static/index.html b/target/classes/static/index.html index 606843b..2936cbf 100644 --- a/target/classes/static/index.html +++ b/target/classes/static/index.html @@ -113,7 +113,7 @@
@@ -288,15 +288,35 @@
-
- MAC: {{ ble.mac }} - RSSI: {{ ble.rssi }} dBm +
+ {{ ble.mac }} +
+
+
+
+
+ {{ ble.rssi }} +
+{{ badge.bluetooth.length - 4 }} 更多...
+ +
+
+ + +
+
+
@@ -330,24 +350,42 @@
-
-
{{ counter.id }}
- {{ formatTime(counter.lastUpdate) }} +
+
+
{{ counter.id }}
+ + RSSI: + {{ counter.RSSI }} dBm + +
+
+ {{ formatTime(counter.lastUpdate) }} + v{{ counter.version }} +
-
+ +
-
{{ counter.inCount }}
- In +
{{ counter.inCount }}
+ 今日进入
-
{{ counter.outCount }}
- Out +
{{ counter.outCount }}
+ 今日离开
-
{{ counter.inCount - counter.outCount }}
- Stay +
{{ counter.inCount - counter.outCount }}
+ 当前滞留
+ + +
+ {{ formatCounterTime(counter.latestData.time) }} + + Rx:{{counter.latestData.rxBat}}% / Tx:{{counter.latestData.txBat}}% + +
@@ -416,7 +454,8 @@ // 实时数据存储 badges: {}, // Map: id -> badge data counters: {}, // Map: id -> counter data - logs: [] + logs: [], + ttsInputs: {} // Map: badgeId -> input string } }, mounted() { @@ -473,10 +512,42 @@ } } - // 2. 处理计数器数据 (识别 type=counter) - else if (data.type === 'counter' && data.id) { - this.counters[data.id] = { - ...data, + // 2. 处理计数器数据 (识别 type=counter OR 含有 uuid 字段的复杂JSON) + else if ((data.type === 'counter' && data.id) || (data.uuid && data.data && Array.isArray(data.data))) { + // 统一 ID 字段 + const id = data.id || data.uuid; + + // 尝试解析复杂 JSON 结构 + let totalIn = data.inCount || 0; + let totalOut = data.outCount || 0; + let latestData = null; + + if (data.data && Array.isArray(data.data)) { + // 累加 data 数组中的流量,或者取最新一条(取决于业务逻辑,这里假设是累积值或者我们显示最新状态) + // 通常计数器上报的是当次时间段内的增量或者当前的累计值。 + // 这里假设我们需要展示最新的累积值,或者自行累加。 + // 简化处理:显示最新一条数据的 in/out,或者如果后端没做累加,前端简单累加 + // 由于示例数据看起来像是一段时间的记录,我们取最后一条作为"最新状态" + + // 找出时间最近的一条 + const sortedData = [...data.data].sort((a, b) => b.time.localeCompare(a.time)); + if (sortedData.length > 0) { + latestData = sortedData[0]; + // 如果 JSON 里的 in/out 是累计值,直接使用;如果是增量,需要累加。 + // 根据示例 "in": 22,看起来像累计值。 + totalIn = latestData.in || 0; // Fix: use || 0 to avoid undefined + totalOut = latestData.out || 0; // Fix: use || 0 to avoid undefined + } + } + + this.counters[id] = { + id: id, + // 优先使用外层字段,没有则使用解析出的字段 + inCount: totalIn, + outCount: totalOut, + RSSI: data.RSSI, + version: data.version, + latestData: latestData, lastUpdate: now }; } @@ -615,7 +686,11 @@ // 辅助函数 formatTime(date) { if (!date) return ''; - return new Date(date).toLocaleTimeString(); + try { + return new Date(date).toLocaleTimeString(); + } catch (e) { + return ''; + } }, getBatteryColor(level) { if (level > 60) return 'text-success'; @@ -640,6 +715,75 @@ action = `长按 ${keyNum}号键`; } return action; + }, + // RSSI 可视化 + getRssiPercentage(rssi) { + // 假设 -100dBm 为 0%,-50dBm 为 100% + const min = -100; + const max = -50; + if (rssi >= max) return 100; + if (rssi <= min) return 0; + return ((rssi - min) / (max - min)) * 100; + }, + getRssiColor(rssi) { + if (rssi >= -60) return '#28a745'; // Green + if (rssi >= -75) return '#ffc107'; // Yellow + if (rssi >= -85) return '#fd7e14'; // Orange + return '#dc3545'; // Red + }, + getRssiColorClass(rssi) { + if (!rssi) return 'text-muted'; + if (rssi >= -60) return 'text-success'; + if (rssi >= -75) return 'text-warning'; + if (rssi >= -85) return 'text-warning'; // Orange-ish + return 'text-danger'; + }, + formatCounterTime(str) { + // 20250501093000 -> 09:30:00 + if (!str || str.length < 14) return str; + return str.substring(8, 10) + ':' + str.substring(10, 12) + ':' + str.substring(12, 14); + }, + // 发送 TTS + async sendTTS(phone) { + const content = this.ttsInputs[phone]; + if (!content || !content.trim()) { + alert('请输入播报内容'); + return; + } + + try { + const formData = new FormData(); + formData.append('phone', phone); + formData.append('content', content); + // Flag 9 = 0x08 (TTS) | 0x01 (紧急/指令) -> 混合模式,确保立刻播报 + formData.append('flag', 9); + + const res = await fetch('/api/v1/device/command/text', { + method: 'POST', + body: formData + }); + const result = await res.json(); + if (result.code === 200) { + // 清空输入框 + this.ttsInputs[phone] = ''; + // 记录日志到界面以便反馈 + this.addLog({ source: 'SYSTEM', message: `向 ${phone} 下发语音: ${content}` }); + } else { + alert('下发失败: ' + result.message); + } + } catch (e) { + alert('网络异常: ' + e.message); + } + }, + // 内部日志辅助 + addLog(data) { + const now = new Date().toLocaleTimeString(); + this.logs.unshift({ + time: now, + source: data.source, + data: data.message || JSON.stringify(data) + }); + if (this.logs.length > 50) this.logs.pop(); } } }).mount('#app')