fix: 数据补报解析
All checks were successful
iot-test-platform CI/CD / build-and-deploy (push) Successful in 24s
All checks were successful
iot-test-platform CI/CD / build-and-deploy (push) Successful in 24s
This commit is contained in:
@@ -32,6 +32,8 @@ public class Consts {
|
|||||||
public static final Integer MSGID_PARAM_QUERY_RESP = 0x0104;
|
public static final Integer MSGID_PARAM_QUERY_RESP = 0x0104;
|
||||||
/** 按键事件上报 **/
|
/** 按键事件上报 **/
|
||||||
public static final Integer MSGID_BUTTON_EVENT = 0x0006;
|
public static final Integer MSGID_BUTTON_EVENT = 0x0006;
|
||||||
|
/** 定位数据批量上传 **/
|
||||||
|
public static final Integer MSGID_LOCATION_BATCH_UPLOAD = 0x0704;
|
||||||
|
|
||||||
|
|
||||||
/** 平台通用应答 **/
|
/** 平台通用应答 **/
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package com.iot.transport.jt808.entity.request;
|
||||||
|
|
||||||
|
import com.iot.transport.jt808.entity.DataPack;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.ToString;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 定位数据批量上传消息 (0x0704)
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@ToString(callSuper = true)
|
||||||
|
public class BatchLocationPack extends DataPack {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据项个数
|
||||||
|
*/
|
||||||
|
private int count;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 位置数据类型
|
||||||
|
* 0:正常位置批量汇报
|
||||||
|
* 1:盲区补报
|
||||||
|
*/
|
||||||
|
private int type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 位置数据列表
|
||||||
|
* 每个元素都是一个完整的 LocationPack (解析内容相同)
|
||||||
|
*/
|
||||||
|
private List<LocationPack> items;
|
||||||
|
|
||||||
|
public BatchLocationPack() {
|
||||||
|
this.items = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public BatchLocationPack(DataPack packageData) {
|
||||||
|
this();
|
||||||
|
this.channel = packageData.getChannel();
|
||||||
|
this.checkSum = packageData.getCheckSum();
|
||||||
|
this.bodyBytes = packageData.getBodyBytes();
|
||||||
|
this.packHead = packageData.getPackHead();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addItem(LocationPack item) {
|
||||||
|
this.items.add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -9,6 +9,7 @@ import com.iot.transport.jt808.entity.DataPack.PackHead;
|
|||||||
import com.iot.transport.jt808.entity.request.LocationPack;
|
import com.iot.transport.jt808.entity.request.LocationPack;
|
||||||
import com.iot.transport.jt808.entity.request.RegisterPack;
|
import com.iot.transport.jt808.entity.request.RegisterPack;
|
||||||
import com.iot.transport.jt808.entity.request.ButtonEventPack;
|
import com.iot.transport.jt808.entity.request.ButtonEventPack;
|
||||||
|
import com.iot.transport.jt808.entity.request.BatchLocationPack;
|
||||||
import com.iot.transport.jt808.entity.request.RegisterPack.TerminalRegInfo;
|
import com.iot.transport.jt808.entity.request.RegisterPack.TerminalRegInfo;
|
||||||
import com.iot.transport.jt808.util.BCDUtil;
|
import com.iot.transport.jt808.util.BCDUtil;
|
||||||
import com.iot.transport.jt808.util.BitUtil;
|
import com.iot.transport.jt808.util.BitUtil;
|
||||||
@@ -226,10 +227,48 @@ public class DataDecoder {
|
|||||||
ret.setKeyState(this.parseIntFromBytes(data, 1, 1));
|
ret.setKeyState(this.parseIntFromBytes(data, 1, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BatchLocationPack toBatchLocationPack(DataPack packageData) {
|
||||||
|
BatchLocationPack ret = new BatchLocationPack(packageData);
|
||||||
|
byte[] data = ret.getBodyBytes();
|
||||||
|
|
||||||
|
// 1. 数据项个数 (WORD)
|
||||||
|
int count = this.parseIntFromBytes(data, 0, 2);
|
||||||
|
ret.setCount(count);
|
||||||
|
|
||||||
|
// 2. 位置数据类型 (BYTE)
|
||||||
|
ret.setType(this.parseIntFromBytes(data, 2, 1));
|
||||||
|
|
||||||
|
// 3. 循环解析位置数据项
|
||||||
|
int index = 3;
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
if (index + 2 > data.length) break;
|
||||||
|
|
||||||
|
// 每一项的数据长度 (WORD)
|
||||||
|
int itemLen = this.parseIntFromBytes(data, index, 2);
|
||||||
|
index += 2;
|
||||||
|
|
||||||
|
if (index + itemLen > data.length) break;
|
||||||
|
|
||||||
|
// 提取该项的字节数组
|
||||||
|
byte[] itemBytes = new byte[itemLen];
|
||||||
|
System.arraycopy(data, index, itemBytes, 0, itemLen);
|
||||||
|
|
||||||
|
// 使用 toLocationInfoUploadMsg 复用解析逻辑
|
||||||
|
// 构造一个临时的 DataPack
|
||||||
|
DataPack itemPack = new DataPack();
|
||||||
|
itemPack.setBodyBytes(itemBytes);
|
||||||
|
LocationPack loc = this.toLocationInfoUploadMsg(itemPack);
|
||||||
|
ret.addItem(loc);
|
||||||
|
|
||||||
|
index += itemLen;
|
||||||
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public LocationPack toLocationInfoUploadMsg(DataPack packageData) {
|
public LocationPack toLocationInfoUploadMsg(DataPack packageData) {
|
||||||
LocationPack ret = new LocationPack(packageData);
|
LocationPack ret = new LocationPack(packageData);
|
||||||
final byte[] data = ret.getBodyBytes();
|
final byte[] data = ret.getBodyBytes();
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import com.iot.transport.jt808.service.handler.terminal.LocationUploadHandler;
|
|||||||
import com.iot.transport.jt808.service.handler.terminal.LoginOutHandler;
|
import com.iot.transport.jt808.service.handler.terminal.LoginOutHandler;
|
||||||
import com.iot.transport.jt808.service.handler.terminal.RegisterHandler;
|
import com.iot.transport.jt808.service.handler.terminal.RegisterHandler;
|
||||||
import com.iot.transport.jt808.service.handler.terminal.ButtonEventHandler;
|
import com.iot.transport.jt808.service.handler.terminal.ButtonEventHandler;
|
||||||
|
import com.iot.transport.jt808.service.handler.terminal.BatchLocationHandler;
|
||||||
|
|
||||||
public class MessageHandlerFactory {
|
public class MessageHandlerFactory {
|
||||||
|
|
||||||
@@ -24,6 +25,7 @@ public class MessageHandlerFactory {
|
|||||||
handlerMap.put(Consts.MSGID_AUTHENTICATION, AuthenticationHandler.class); // 终端鉴权
|
handlerMap.put(Consts.MSGID_AUTHENTICATION, AuthenticationHandler.class); // 终端鉴权
|
||||||
handlerMap.put(Consts.MSGID_LOCATION_UPLOAD, LocationUploadHandler.class); // 位置信息汇报
|
handlerMap.put(Consts.MSGID_LOCATION_UPLOAD, LocationUploadHandler.class); // 位置信息汇报
|
||||||
handlerMap.put(Consts.MSGID_BUTTON_EVENT, ButtonEventHandler.class); // 按键事件上报
|
handlerMap.put(Consts.MSGID_BUTTON_EVENT, ButtonEventHandler.class); // 按键事件上报
|
||||||
|
handlerMap.put(Consts.MSGID_LOCATION_BATCH_UPLOAD, BatchLocationHandler.class); // 批量定位上传
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import com.iot.transport.jt808.entity.request.BatteryVersionInfo;
|
|||||||
import com.iot.transport.jt808.entity.request.BluetoothInfo;
|
import com.iot.transport.jt808.entity.request.BluetoothInfo;
|
||||||
import com.iot.transport.jt808.entity.request.LocationPack;
|
import com.iot.transport.jt808.entity.request.LocationPack;
|
||||||
import com.iot.transport.jt808.entity.request.ButtonEventPack;
|
import com.iot.transport.jt808.entity.request.ButtonEventPack;
|
||||||
|
import com.iot.transport.jt808.entity.request.BatchLocationPack;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class TCPServerHandler extends ChannelInboundHandlerAdapter { // (1)
|
public class TCPServerHandler extends ChannelInboundHandlerAdapter { // (1)
|
||||||
@@ -135,6 +136,32 @@ public class TCPServerHandler extends ChannelInboundHandlerAdapter { // (1)
|
|||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logMap.put("details", packageData.toString() + " (Parse Error: " + e.getMessage() + ")");
|
logMap.put("details", packageData.toString() + " (Parse Error: " + e.getMessage() + ")");
|
||||||
}
|
}
|
||||||
|
} else if (msgId == Consts.MSGID_LOCATION_BATCH_UPLOAD || msgId == 0x0704) {
|
||||||
|
try {
|
||||||
|
BatchLocationPack batchPack = this.decoder.toBatchLocationPack(packageData);
|
||||||
|
logMap.put("details", "Batch Upload: " + batchPack.getCount() + " items");
|
||||||
|
logMap.put("type", "badge");
|
||||||
|
logMap.put("id", header.getTerminalPhone());
|
||||||
|
|
||||||
|
// Use the latest location item to update the UI
|
||||||
|
if (batchPack.getItems() != null && !batchPack.getItems().isEmpty()) {
|
||||||
|
LocationPack lastLoc = batchPack.getItems().get(batchPack.getItems().size() - 1);
|
||||||
|
|
||||||
|
// Location
|
||||||
|
Map<String, Object> locMap = new HashMap<>();
|
||||||
|
locMap.put("lat", lastLoc.getLatitude());
|
||||||
|
locMap.put("lon", lastLoc.getLongitude());
|
||||||
|
logMap.put("location", locMap);
|
||||||
|
|
||||||
|
// Battery
|
||||||
|
BatteryVersionInfo batInfo = lastLoc.getBatteryVersionInfo();
|
||||||
|
if (batInfo != null) {
|
||||||
|
logMap.put("battery", batInfo.getBatteryLevel());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logMap.put("details", packageData.toString() + " (Parse Error: " + e.getMessage() + ")");
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
logMap.put("details", packageData.toString());
|
logMap.put("details", packageData.toString());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package com.iot.transport.jt808.service.handler.terminal;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.iot.transport.jt808.entity.DataPack;
|
||||||
|
import com.iot.transport.jt808.entity.DataPack.PackHead;
|
||||||
|
import com.iot.transport.jt808.entity.request.BatchLocationPack;
|
||||||
|
import com.iot.transport.jt808.entity.request.LocationPack;
|
||||||
|
import com.iot.transport.jt808.entity.response.ServerBodyPack;
|
||||||
|
import com.iot.transport.jt808.service.handler.MessageHandler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 定位数据批量上传 (0x0704) ==> 平台通用应答
|
||||||
|
*/
|
||||||
|
public class BatchLocationHandler extends MessageHandler {
|
||||||
|
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(getClass());
|
||||||
|
|
||||||
|
public BatchLocationHandler() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void process(DataPack packageData) {
|
||||||
|
|
||||||
|
PackHead header = packageData.getPackHead();
|
||||||
|
logger.info("[批量定位],msgid={}, phone={},flowid={}", header.getId(), header.getTerminalPhone(), header.getFlowId());
|
||||||
|
|
||||||
|
try {
|
||||||
|
BatchLocationPack msg = this.decoder.toBatchLocationPack(packageData);
|
||||||
|
logger.info("批量定位数据: Count={}, Type={}, Items={}", msg.getCount(), msg.getType(), msg.getItems().size());
|
||||||
|
|
||||||
|
// 可以在这里处理每个 LocationPack,例如入库或推送到前端
|
||||||
|
for (LocationPack item : msg.getItems()) {
|
||||||
|
// TODO: Process each location item
|
||||||
|
logger.debug(" -> Item: Lat={}, Lon={}, Time={}", item.getLatitude(), item.getLongitude(), item.getTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 回复通用应答
|
||||||
|
int flowId = super.getFlowId(msg.getChannel());
|
||||||
|
ServerBodyPack respMsgBody = new ServerBodyPack(header.getFlowId(), header.getId(), ServerBodyPack.success);
|
||||||
|
byte[] bs = this.msgEncoder.encode4ServerCommonRespMsg(msg, respMsgBody, flowId);
|
||||||
|
super.send2Client(msg.getChannel(), bs);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("[批量定位]错误, err={}", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -113,7 +113,7 @@
|
|||||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark flex-shrink-0">
|
<nav class="navbar navbar-expand-lg navbar-dark bg-dark flex-shrink-0">
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<a class="navbar-brand" href="#">
|
<a class="navbar-brand" href="#">
|
||||||
<i class="fas fa-network-wired me-2"></i>IoT 数据监控平台
|
<i class="fas fa-network-wired me-2"></i>IoT 数据测试平台
|
||||||
</a>
|
</a>
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
<span class="text-light me-3">
|
<span class="text-light me-3">
|
||||||
|
|||||||
@@ -149,6 +149,23 @@
|
|||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<div v-if="mode === 'badge'">
|
<div v-if="mode === 'badge'">
|
||||||
|
<div v-if="mode === 'command'">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label small text-muted">接口类型</label>
|
||||||
|
<select v-model="commandForm.apiType" class="form-select form-select-sm" @change="updateJsonTemplate">
|
||||||
|
<option value="location">位置查询 (8201)</option>
|
||||||
|
<option value="text">文本下发 (8300)</option>
|
||||||
|
<option value="general">通用指令 (API)</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label small text-muted">请求参数 (JSON)</label>
|
||||||
|
<textarea v-model="commandForm.jsonBody" class="form-control font-monospace form-control-sm" rows="8"></textarea>
|
||||||
|
</div>
|
||||||
|
<button @click="sendCommand" class="btn btn-primary btn-sm w-100">
|
||||||
|
<i class="fas fa-terminal me-2"></i>发送指令
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label small text-muted">工牌 ID</label>
|
<label class="form-label small text-muted">工牌 ID</label>
|
||||||
<input v-model="badgeForm.id" class="form-control form-control-sm" placeholder="BADGE-001">
|
<input v-model="badgeForm.id" class="form-control form-control-sm" placeholder="BADGE-001">
|
||||||
@@ -199,23 +216,6 @@
|
|||||||
<button @click="sendCustomJson" class="btn btn-secondary btn-sm w-100">发送自定义数据</button>
|
<button @click="sendCustomJson" class="btn btn-secondary btn-sm w-100">发送自定义数据</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="mode === 'command'">
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label small text-muted">接口类型</label>
|
|
||||||
<select v-model="commandForm.apiType" class="form-select form-select-sm" @change="updateJsonTemplate">
|
|
||||||
<option value="location">位置查询 (8201)</option>
|
|
||||||
<option value="text">文本下发 (8300)</option>
|
|
||||||
<option value="general">通用指令 (API)</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label small text-muted">请求参数 (JSON)</label>
|
|
||||||
<textarea v-model="commandForm.jsonBody" class="form-control font-monospace form-control-sm" rows="8"></textarea>
|
|
||||||
</div>
|
|
||||||
<button @click="sendCommand" class="btn btn-primary btn-sm w-100">
|
|
||||||
<i class="fas fa-terminal me-2"></i>发送指令
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -278,7 +278,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
<div v-if="badge.location" class="text-dark fw-bold mb-1" title="点击查看详情">
|
<div v-if="badge.location" class="text-dark fw-bold mb-1" title="点击查看详情">
|
||||||
{{ badge.location.lat.toFixed(4) }}, {{ badge.location.lon.toFixed(4) }}
|
{{ badge.location.lat.toFixed(6) }}, {{ badge.location.lon.toFixed(6) }}
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="text-muted mb-1">-</div>
|
<div v-else class="text-muted mb-1">-</div>
|
||||||
<div class="text-muted" style="font-size: 0.7rem;">GPS坐标</div>
|
<div class="text-muted" style="font-size: 0.7rem;">GPS坐标</div>
|
||||||
@@ -288,14 +288,21 @@
|
|||||||
|
|
||||||
<!-- 详细蓝牙列表 (折叠展示) -->
|
<!-- 详细蓝牙列表 (折叠展示) -->
|
||||||
<div v-if="badge.bluetooth && badge.bluetooth.length > 0" class="mt-2 bg-light rounded p-2" style="font-size: 0.75rem;">
|
<div v-if="badge.bluetooth && badge.bluetooth.length > 0" class="mt-2 bg-light rounded p-2" style="font-size: 0.75rem;">
|
||||||
<div v-for="(ble, idx) in badge.bluetooth.slice(0, 2)" :key="idx" class="d-flex justify-content-between text-muted">
|
<div v-for="(ble, idx) in badge.bluetooth.slice(0, 4)" :key="idx" class="d-flex justify-content-between text-muted">
|
||||||
<span>MAC: {{ ble.mac }}</span>
|
<span>MAC: {{ ble.mac }}</span>
|
||||||
<span>RSSI: {{ ble.rssi }}</span>
|
<span>RSSI: {{ ble.rssi }} dBm</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="badge.bluetooth.length > 2" class="text-center text-primary" style="font-size: 0.7rem;">
|
<div v-if="badge.bluetooth.length > 4" class="text-center text-primary" style="font-size: 0.7rem;">
|
||||||
+{{ badge.bluetooth.length - 2 }} 更多...
|
+{{ badge.bluetooth.length - 4 }} 更多...
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 按键事件提示 -->
|
||||||
|
<div v-if="badge.lastButtonEvent" class="mt-2 alert alert-warning p-1 mb-0 text-center" style="font-size: 0.75rem;">
|
||||||
|
<i class="fas fa-hand-pointer me-1"></i>
|
||||||
|
{{ formatButtonEvent(badge.lastButtonEvent) }}
|
||||||
|
<span class="text-muted ms-1">({{ formatTime(badge.lastButtonEvent.timestamp) }})</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -444,10 +451,26 @@
|
|||||||
|
|
||||||
// 1. 处理工牌数据 (识别 type=badge)
|
// 1. 处理工牌数据 (识别 type=badge)
|
||||||
if (data.type === 'badge' && data.id) {
|
if (data.type === 'badge' && data.id) {
|
||||||
|
// 合并旧数据,保留 lastButtonEvent 如果新数据没有覆盖它
|
||||||
|
const oldData = this.badges[data.id] || {};
|
||||||
|
const newButtonEvent = data.buttonEvent;
|
||||||
|
|
||||||
this.badges[data.id] = {
|
this.badges[data.id] = {
|
||||||
|
...oldData,
|
||||||
...data,
|
...data,
|
||||||
lastUpdate: now
|
lastUpdate: now,
|
||||||
|
// 如果这次是按键事件,更新 lastButtonEvent;否则保留旧的(可以加个超时自动清除逻辑)
|
||||||
|
lastButtonEvent: newButtonEvent || oldData.lastButtonEvent
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 简单的超时清除按键提示 (5秒后消失)
|
||||||
|
if (newButtonEvent) {
|
||||||
|
setTimeout(() => {
|
||||||
|
if (this.badges[data.id] && this.badges[data.id].lastButtonEvent === newButtonEvent) {
|
||||||
|
this.badges[data.id].lastButtonEvent = null;
|
||||||
|
}
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 处理计数器数据 (识别 type=counter)
|
// 2. 处理计数器数据 (识别 type=counter)
|
||||||
@@ -603,6 +626,20 @@
|
|||||||
if (source === 'SYSTEM') return 'bg-info text-dark';
|
if (source === 'SYSTEM') return 'bg-info text-dark';
|
||||||
if (source === 'TCP') return 'bg-warning text-dark';
|
if (source === 'TCP') return 'bg-warning text-dark';
|
||||||
return 'bg-success';
|
return 'bg-success';
|
||||||
|
},
|
||||||
|
formatButtonEvent(evt) {
|
||||||
|
if (!evt) return '';
|
||||||
|
let action = '按键';
|
||||||
|
// 0x01-0x0A: 短按
|
||||||
|
if (evt.keyId >= 0x01 && evt.keyId <= 0x0A) {
|
||||||
|
action = `短按 ${evt.keyId}号键`;
|
||||||
|
}
|
||||||
|
// 0x0B-0x14: 长按
|
||||||
|
else if (evt.keyId >= 0x0B && evt.keyId <= 0x14) {
|
||||||
|
const keyNum = evt.keyId - 0x0A;
|
||||||
|
action = `长按 ${keyNum}号键`;
|
||||||
|
}
|
||||||
|
return action;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).mount('#app')
|
}).mount('#app')
|
||||||
|
|||||||
Reference in New Issue
Block a user