[1078] 支持终端属性和链路检测的UI

This commit is contained in:
lin
2025-07-16 11:00:43 +08:00
parent 5874d4d1fa
commit 45af124eb0
9 changed files with 308 additions and 54 deletions

View File

@@ -49,7 +49,7 @@ public class Jt808Decoder extends ByteToMessageDecoder {
try {
// 按照部标定义执行校验和转义
ByteBuf buf = unEscapeAndCheck(in);
buf.retain();
Header header = new Header();
header.setMsgId(ByteBufUtil.hexDump(buf.readSlice(2)));
header.setMsgPro(buf.readUnsignedShort());
@@ -79,7 +79,7 @@ public class Jt808Decoder extends ByteToMessageDecoder {
log.error("get msgId is null {}", header.getMsgId());
return;
}
buf.retain();
Rs decode = handler.decode(buf, header, session, service);
ApplicationEvent applicationEvent = handler.getEvent();
if (applicationEvent != null) {

View File

@@ -9,6 +9,7 @@ import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.util.ReferenceCountUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationEventPublisher;
@@ -35,6 +36,8 @@ public class Jt808Handler extends ChannelInboundHandlerAdapter {
} else {
ctx.fireChannelRead(msg);
}
// 读取完成后的消息释放
ReferenceCountUtil.release(msg);
}
@Override

View File

@@ -506,17 +506,10 @@ public class JT1078Controller {
@Operation(summary = "JT-链路检测", security = @SecurityRequirement(name = JwtUtils.HEADER))
@Parameter(name = "phoneNumber", description = "设备编号", required = true)
@GetMapping("/link-detection")
public WVPResult<Integer> linkDetection(String phoneNumber){
public Integer linkDetection(String phoneNumber){
log.info("[JT-链路检测] phoneNumber: {}", phoneNumber);
int result = service.linkDetection(phoneNumber);
if (result == 0) {
return WVPResult.success(result);
}else {
WVPResult<Integer> fail = WVPResult.fail(ErrorCode.ERROR100);
fail.setData(result);
return fail;
}
return service.linkDetection(phoneNumber);
}
@Operation(summary = "JT-文本信息下发", security = @SecurityRequirement(name = JwtUtils.HEADER))

View File

@@ -228,5 +228,32 @@ export function setConfig(data) {
data: data
})
}
export function queryAttribute(phoneNumber) {
return request({
method: 'get',
url: `/api/jt1078/attribute`,
params: {
phoneNumber: phoneNumber
}
})
}
export function linkDetection(phoneNumber) {
return request({
method: 'get',
url: `/api/jt1078/link-detection`,
params: {
phoneNumber: phoneNumber
}
})
}
export function queryPosition(phoneNumber) {
return request({
method: 'get',
url: `/api/jt1078/position-info`,
params: {
phoneNumber: phoneNumber
}
})
}

View File

@@ -1,11 +1,11 @@
import {
add,
addChannel, controlPlayback, deleteDevice,
fillLight, getRecordTempUrl,
play, ptz,
fillLight, getRecordTempUrl, linkDetection,
play, ptz, queryAttribute,
queryChannels, queryConfig,
queryDeviceById,
queryDevices, queryRecordList, setConfig, startPlayback,
queryDevices, queryPosition, queryRecordList, setConfig, startPlayback,
stopPlay, stopPlayback, update,
updateChannel, wiper
} from '@/api/jtDevice'
@@ -210,6 +210,36 @@ const actions = {
reject(error)
})
})
},
queryAttribute({ commit }, phoneNumber) {
return new Promise((resolve, reject) => {
queryAttribute(phoneNumber).then(response => {
const { data } = response
resolve(data)
}).catch(error => {
reject(error)
})
})
},
linkDetection({ commit }, phoneNumber) {
return new Promise((resolve, reject) => {
linkDetection(phoneNumber).then(response => {
const { data } = response
resolve(data)
}).catch(error => {
reject(error)
})
})
},
queryPosition({ commit }, phoneNumber) {
return new Promise((resolve, reject) => {
queryPosition(phoneNumber).then(response => {
const { data } = response
resolve(data)
}).catch(error => {
reject(error)
})
})
}
}

View File

@@ -57,7 +57,7 @@
<template slot-scope="scope">
<el-button
size="medium"
:disabled="device == null || device.online === 0"
:disabled="device == null || !device.status"
icon="el-icon-video-play"
type="text"
@click="sendDevicePush(scope.row)"
@@ -66,13 +66,21 @@
<el-button
v-if="!!scope.row.stream"
size="medium"
:disabled="device == null || device.online === 0"
:disabled="device == null || !device.status"
icon="el-icon-switch-button"
type="text"
style="color: #f56c6c"
@click="stopDevicePush(scope.row)"
>停止
</el-button>
<el-button
size="medium"
:disabled="device == null || !device.status"
icon="el-icon-camera"
type="text"
@click="shooting(scope.row)"
>抓图
</el-button>
<el-divider direction="vertical" />
<el-button
size="medium"
@@ -88,13 +96,11 @@
更多功能<i class="el-icon-arrow-down el-icon--right" />
</el-button>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="records" :disabled="device == null || device.online === 0">
<el-dropdown-item command="records" :disabled="device == null || !device.status">
设备录像</el-dropdown-item>
<el-dropdown-item command="cloudRecords" :disabled="device == null || device.online === 0">
<el-dropdown-item command="cloudRecords" :disabled="device == null || !device.status">
云端录像</el-dropdown-item>
<el-dropdown-item command="shooting" v-bind:disabled="device == null || device.online === 0" >
抓图</el-dropdown-item>
<el-dropdown-item command="searchData" v-bind:disabled="device == null || device.online === 0" >
<el-dropdown-item command="searchData" v-bind:disabled="device == null || !device.status" >
数据检索</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
@@ -242,8 +248,6 @@ export default {
this.queryRecords(itemData)
} else if (command === 'cloudRecords') {
this.queryCloudRecords(itemData)
} else if (command === 'shooting') {
this.shooting(itemData)
} else {
this.$message.info('尚不支持')
}
@@ -322,6 +326,7 @@ export default {
},
// 编辑
shooting(row) {
this.$message.success('已申请截图,完成后自动下载', { closed: true })
// 文件下载地址
const baseUrl = window.baseUrl ? window.baseUrl : ''
const fileUrl = ((process.env.NODE_ENV === 'development') ? process.env.VUE_APP_BASE_API : baseUrl) + `/api/jt1078/snap?phoneNumber=${this.device.phoneNumber}&channelId=${row.channelId}`
@@ -342,13 +347,10 @@ export default {
link.href = window.URL.createObjectURL(blob)
link.download = `${this.device.phoneNumber}-${row.channelId}-${dayjs().format('YYYYMMDDHHmmss')}.jpg` // 设置下载文件名替换filename.ext为实际的文件名和扩展名
document.body.appendChild(link)
// 模拟点击
link.click()
// 移除虚拟链接元素
document.body.removeChild(link)
this.$message.success('已申请截图', { closed: true })
})
.catch(error => console.error('下载失败:', error))
}

View File

@@ -0,0 +1,77 @@
<template>
<div id="configInfo">
<el-dialog
v-el-drag-dialog
title="终端属性"
width="=80%"
top="2rem"
:close-on-click-modal="false"
:visible.sync="showDialog"
:destroy-on-close="true"
@close="close()"
>
<div id="shared">
<el-descriptions title="基本属性" :column="2" v-if="attributeData" style="margin-bottom: 1rem;">
<el-descriptions-item label="制造商ID">{{ attributeData.makerId }}</el-descriptions-item>
<el-descriptions-item label="终端型号">{{ attributeData.deviceModel }}</el-descriptions-item>
<el-descriptions-item label="终端ID">{{ attributeData.terminalId }}</el-descriptions-item>
<el-descriptions-item label="SIM卡ICCID">{{ attributeData.iccId }}</el-descriptions-item>
<el-descriptions-item label="硬件版本号">{{ attributeData.hardwareVersion }}</el-descriptions-item>
<el-descriptions-item label="固件版本号">{{ attributeData.firmwareVersion }}</el-descriptions-item>
</el-descriptions>
<el-descriptions title="终端类型" :column="2" v-if="attributeData.type" style="margin-bottom: 1rem;">
<el-descriptions-item label="客运车辆">{{ attributeData.type.passengerVehicles }}</el-descriptions-item>
<el-descriptions-item label="危险品车辆">{{ attributeData.type.dangerousGoodsVehicles }}</el-descriptions-item>
<el-descriptions-item label="普通货运车辆">{{ attributeData.type.freightVehicles }}</el-descriptions-item>
<el-descriptions-item label="出租车辆">{{ attributeData.type.rentalVehicles }}</el-descriptions-item>
<el-descriptions-item label="硬盘录像">{{ attributeData.type.hardDiskRecording?'支持': '不支持' }}</el-descriptions-item>
<el-descriptions-item label="类型">{{ attributeData.type.splittingMachine?'分体机': '一体机' }}</el-descriptions-item>
<el-descriptions-item label="适用挂车">{{ attributeData.type.trailer?'是': '否' }}</el-descriptions-item>
</el-descriptions>
<el-descriptions title="GNSS模块" :column="2" v-if="attributeData.gnssAttribute" style="margin-bottom: 1rem;">
<el-descriptions-item label="GPS卫星">{{ attributeData.gnssAttribute.gps?'支持': '不支持' }}</el-descriptions-item>
<el-descriptions-item label="北斗卫星">{{ attributeData.gnssAttribute.beidou?'支持': '不支持' }}</el-descriptions-item>
<el-descriptions-item label="GLONASS卫星">{{ attributeData.gnssAttribute.glonass?'支持': '不支持' }}</el-descriptions-item>
<el-descriptions-item label="Galileo卫星">{{ attributeData.gnssAttribute.gaLiLeo?'支持': '不支持' }}</el-descriptions-item>
</el-descriptions>
<el-descriptions title="通信模块" :column="2" v-if="attributeData.communicationModuleAttribute" style="margin-bottom: 1rem;">
<el-descriptions-item label="GPRS通信">{{ attributeData.communicationModuleAttribute.gprs?'支持': '不支持' }}</el-descriptions-item>
<el-descriptions-item label="CDMA通信">{{ attributeData.communicationModuleAttribute.cdma?'支持': '不支持' }}</el-descriptions-item>
<el-descriptions-item label="TD-SCDMA通信">{{ attributeData.communicationModuleAttribute.tdScdma?'支持': '不支持' }}</el-descriptions-item>
<el-descriptions-item label="WCDMA通信">{{ attributeData.communicationModuleAttribute.wcdma?'支持': '不支持' }}</el-descriptions-item>
<el-descriptions-item label="CDMA2000通信">{{ attributeData.communicationModuleAttribute.cdma2000?'支持': '不支持' }}</el-descriptions-item>
<el-descriptions-item label="TD-LTE通信">{{ attributeData.communicationModuleAttribute.tdLte?'支持': '不支持' }}</el-descriptions-item>
<el-descriptions-item label="其他通信方式">{{ attributeData.communicationModuleAttribute.other?'支持': '不支持' }}</el-descriptions-item>
</el-descriptions>
</div>
</el-dialog>
</div>
</template>
<script>
import elDragDialog from '@/directive/el-drag-dialog'
export default {
name: 'ConfigInfo',
directives: { elDragDialog },
props: {},
data() {
return {
showDialog: false,
attributeData: null
}
},
computed: {},
created() {},
methods: {
openDialog: function(data) {
this.showDialog = true
this.attributeData = data
},
close: function() {
this.showDialog = false
}
}
}
</script>

View File

@@ -0,0 +1,77 @@
<template>
<div id="configInfo">
<el-dialog
v-el-drag-dialog
title="位置信息"
width="=80%"
top="2rem"
:close-on-click-modal="false"
:visible.sync="showDialog"
:destroy-on-close="true"
@close="close()"
>
<div id="shared">
<el-descriptions title="基本属性" :column="2" v-if="position" style="margin-bottom: 1rem;">
<el-descriptions-item label="制造商ID">{{ attributeData.makerId }}</el-descriptions-item>
<el-descriptions-item label="终端型号">{{ attributeData.deviceModel }}</el-descriptions-item>
<el-descriptions-item label="终端ID">{{ attributeData.terminalId }}</el-descriptions-item>
<el-descriptions-item label="SIM卡ICCID">{{ attributeData.iccId }}</el-descriptions-item>
<el-descriptions-item label="硬件版本号">{{ attributeData.hardwareVersion }}</el-descriptions-item>
<el-descriptions-item label="固件版本号">{{ attributeData.firmwareVersion }}</el-descriptions-item>
</el-descriptions>
<el-descriptions title="终端类型" :column="2" v-if="attributeData.type" style="margin-bottom: 1rem;">
<el-descriptions-item label="客运车辆">{{ attributeData.type.passengerVehicles }}</el-descriptions-item>
<el-descriptions-item label="危险品车辆">{{ attributeData.type.dangerousGoodsVehicles }}</el-descriptions-item>
<el-descriptions-item label="普通货运车辆">{{ attributeData.type.freightVehicles }}</el-descriptions-item>
<el-descriptions-item label="出租车辆">{{ attributeData.type.rentalVehicles }}</el-descriptions-item>
<el-descriptions-item label="硬盘录像">{{ attributeData.type.hardDiskRecording?'支持': '不支持' }}</el-descriptions-item>
<el-descriptions-item label="类型">{{ attributeData.type.splittingMachine?'分体机': '一体机' }}</el-descriptions-item>
<el-descriptions-item label="适用挂车">{{ attributeData.type.trailer?'是': '否' }}</el-descriptions-item>
</el-descriptions>
<el-descriptions title="GNSS模块" :column="2" v-if="attributeData.gnssAttribute" style="margin-bottom: 1rem;">
<el-descriptions-item label="GPS卫星">{{ attributeData.gnssAttribute.gps?'支持': '不支持' }}</el-descriptions-item>
<el-descriptions-item label="北斗卫星">{{ attributeData.gnssAttribute.beidou?'支持': '不支持' }}</el-descriptions-item>
<el-descriptions-item label="GLONASS卫星">{{ attributeData.gnssAttribute.glonass?'支持': '不支持' }}</el-descriptions-item>
<el-descriptions-item label="Galileo卫星">{{ attributeData.gnssAttribute.gaLiLeo?'支持': '不支持' }}</el-descriptions-item>
</el-descriptions>
<el-descriptions title="通信模块" :column="2" v-if="attributeData.communicationModuleAttribute" style="margin-bottom: 1rem;">
<el-descriptions-item label="GPRS通信">{{ attributeData.communicationModuleAttribute.gprs?'支持': '不支持' }}</el-descriptions-item>
<el-descriptions-item label="CDMA通信">{{ attributeData.communicationModuleAttribute.cdma?'支持': '不支持' }}</el-descriptions-item>
<el-descriptions-item label="TD-SCDMA通信">{{ attributeData.communicationModuleAttribute.tdScdma?'支持': '不支持' }}</el-descriptions-item>
<el-descriptions-item label="WCDMA通信">{{ attributeData.communicationModuleAttribute.wcdma?'支持': '不支持' }}</el-descriptions-item>
<el-descriptions-item label="CDMA2000通信">{{ attributeData.communicationModuleAttribute.cdma2000?'支持': '不支持' }}</el-descriptions-item>
<el-descriptions-item label="TD-LTE通信">{{ attributeData.communicationModuleAttribute.tdLte?'支持': '不支持' }}</el-descriptions-item>
<el-descriptions-item label="其他通信方式">{{ attributeData.communicationModuleAttribute.other?'支持': '不支持' }}</el-descriptions-item>
</el-descriptions>
</div>
</el-dialog>
</div>
</template>
<script>
import elDragDialog from '@/directive/el-drag-dialog'
export default {
name: 'ConfigInfo',
directives: { elDragDialog },
props: {},
data() {
return {
showDialog: false,
attributeData: null
}
},
computed: {},
created() {},
methods: {
openDialog: function(data) {
this.showDialog = true
this.attributeData = data
},
close: function() {
this.showDialog = false
}
}
}
</script>

View File

@@ -105,32 +105,30 @@
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="params" :disabled="!scope.row.status">
终端参数</el-dropdown-item>
<!-- <el-dropdown-item command="attribute" v-bind:disabled="!scope.row.status">-->
<!-- 终端属性</el-dropdown-item>-->
<!-- <el-dropdown-item command="connection" :disabled="!scope.row.status">-->
<!-- 终端连接</el-dropdown-item>-->
<!-- <el-dropdown-item command="linkDetection" v-bind:disabled="!scope.row.status" >-->
<!-- 链路检测</el-dropdown-item>-->
<!-- <el-dropdown-item command="position" v-bind:disabled="!scope.row.status" >-->
<!-- 位置信息</el-dropdown-item>-->
<!-- <el-dropdown-item command="textMsg" v-bind:disabled="!scope.row.status" >-->
<!-- 文本信息</el-dropdown-item>-->
<!-- <el-dropdown-item command="telephoneCallback" v-bind:disabled="!scope.row.status" >-->
<!-- 电话回拨</el-dropdown-item>-->
<!-- <el-dropdown-item command="setPhoneBook" v-bind:disabled="!scope.row.status" >-->
<!-- 设置电话本</el-dropdown-item>-->
<!-- <el-dropdown-item command="tempPositionTracking" v-bind:disabled="!scope.row.status" >-->
<!-- 临时跟踪</el-dropdown-item>-->
<!-- <el-dropdown-item command="reset" v-bind:disabled="!scope.row.status" >-->
<!-- 终端复位</el-dropdown-item>-->
<!-- <el-dropdown-item command="factoryReset" v-bind:disabled="!scope.row.status" >-->
<!-- 恢复出厂</el-dropdown-item>-->
<!-- <el-dropdown-item command="door" v-bind:disabled="!scope.row.status" >-->
<!-- 车门控制</el-dropdown-item>-->
<!-- <el-dropdown-item command="driverInfo" v-bind:disabled="!scope.row.status" >-->
<!-- 驾驶员信息</el-dropdown-item>-->
<!-- <el-dropdown-item command="mediaAttribute" v-bind:disabled="!scope.row.status" >-->
<!-- 音视频属性</el-dropdown-item>-->
<el-dropdown-item command="attribute" v-bind:disabled="!scope.row.status">
终端属性</el-dropdown-item>
<el-dropdown-item command="linkDetection" v-bind:disabled="!scope.row.status" >
链路检测</el-dropdown-item>
<el-dropdown-item command="position" v-bind:disabled="!scope.row.status" >
位置信息</el-dropdown-item>
<el-dropdown-item command="textMsg" v-bind:disabled="!scope.row.status" >
文本信息</el-dropdown-item>
<el-dropdown-item command="telephoneCallback" v-bind:disabled="!scope.row.status" >
电话回拨</el-dropdown-item>
<el-dropdown-item command="setPhoneBook" v-bind:disabled="!scope.row.status" >
设置电话本</el-dropdown-item>
<el-dropdown-item command="tempPositionTracking" v-bind:disabled="!scope.row.status" >
临时跟踪</el-dropdown-item>
<el-dropdown-item command="reset" v-bind:disabled="!scope.row.status" >
终端复位</el-dropdown-item>
<el-dropdown-item command="factoryReset" v-bind:disabled="!scope.row.status" >
恢复出厂</el-dropdown-item>
<el-dropdown-item command="door" v-bind:disabled="!scope.row.status" >
车门控制</el-dropdown-item>
<el-dropdown-item command="driverInfo" v-bind:disabled="!scope.row.status" >
驾驶员信息</el-dropdown-item>
<el-dropdown-item command="mediaAttribute" v-bind:disabled="!scope.row.status" >
音视频属性</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</template>
@@ -148,17 +146,21 @@
/>
<deviceEdit ref="deviceEdit" />
<configInfo ref="configInfo" />
<attribute ref="attribute" />
<position ref="position" />
</div>
</template>
<script>
import deviceEdit from './edit.vue'
import configInfo from '../dialog/configInfo.vue'
import attribute from './dialog/attribute.vue'
import position from './dialog/position.vue'
export default {
name: 'App',
components: {
deviceEdit, configInfo
deviceEdit, configInfo, attribute, position
},
data() {
return {
@@ -259,6 +261,12 @@ export default {
this.showParam(itemData)
} else if (command === 'connection') {
// this.queryCloudRecords(itemData)
} else if (command === 'attribute') {
this.queryAttribute(itemData)
} else if (command === 'linkDetection') {
this.linkDetection(itemData)
} else if (command === 'position') {
this.queryPosition(itemData)
} else {
this.$message.info('尚不支持')
}
@@ -269,8 +277,45 @@ export default {
this.serverId = data.addOn.serverId
this.$refs.configInfo.openDialog(data, 'jt1078Config')
})
},
queryAttribute: function(itemData) {
this.$store.dispatch('jtDevice/queryAttribute', itemData.phoneNumber)
.then((data) => {
this.$refs.attribute.openDialog(data)
})
},
queryPosition: function(itemData) {
this.$store.dispatch('jtDevice/queryPosition', itemData.phoneNumber)
.then((data) => {
this.$refs.position.openDialog(data)
})
},
linkDetection: function(itemData) {
this.$store.dispatch('jtDevice/linkDetection', itemData.phoneNumber)
.then((data) => {
if (data === 0) {
this.$message.success({
showClose: true,
message: '成功'
})
}else if (data === 1) {
this.$message.error({
showClose: true,
message: '失败'
})
}else if (data === 2) {
this.$message.error({
showClose: true,
message: '消息有误'
})
}else if (data === 3) {
this.$message.error({
showClose: true,
message: '不支持此消息'
})
}
})
}
}
}
</script>