20260126:模型排序&高德地图

This commit is contained in:
George 2026-01-26 19:17:47 +08:00
parent 652a32b797
commit b770da12e3
13 changed files with 5348 additions and 5153 deletions

View File

@ -102,7 +102,7 @@ spring:
redis: redis:
host: localhost # 地址 host: localhost # 地址
port: 6379 # 端口,默认为6379 port: 6379 # 端口,默认为6379
database: 3 # 数据库索引 database: 0 # 数据库索引
password: fastbee # 密码 password: fastbee # 密码
timeout: 10s # 连接超时时间 timeout: 10s # 连接超时时间
lettuce: lettuce:

View File

@ -50,8 +50,11 @@ public class TCPBootStrap {
.setPort(port) .setPort(port)
//设置报文最大长度 modbus-rtu //设置报文最大长度 modbus-rtu
.setMaxFrameLength(1024) .setMaxFrameLength(1024)
// 此处就是ASCII和注册包粘包了,无需strip掉 “分隔符” 即可 /*
.setDelimiters(new Delimiter(new byte[]{0x7e}, false)) * 此处就是ASCII和注册包粘包了,无需strip掉 “分隔符” 即可
* 最原始.setDelimiters(new Delimiter(new byte[]{0x7e}, true))
*/
.setDelimiters(new Delimiter(new byte[]{0x0A}, true))
.setDecoder(messageAdapter) .setDecoder(messageAdapter)
.setEncoder(messageAdapter) .setEncoder(messageAdapter)
.setHandlerMapping(handlerMapping) .setHandlerMapping(handlerMapping)

View File

@ -125,13 +125,12 @@ public class DelimiterBasedFrameDecoder extends ByteToMessageDecoder {
if (minDelim != null) { if (minDelim != null) {
int minDelimLength = minDelim.value.length; int minDelimLength = minDelim.value.length;
ByteBuf frame = null; ByteBuf frame = null;
boolean keepDelimiterForNextFrame = shouldKeepDelimiterForNextFrame(minDelim) && minFrameLength > 0;
if (discardingTooLongFrame) { if (discardingTooLongFrame) {
// 如果true,将长度不符合报文丢弃 // 如果true,将长度不符合报文丢弃
// 初始化原来的值 // 初始化原来的值
discardingTooLongFrame = false; discardingTooLongFrame = false;
buffer.skipBytes(minFrameLength + (keepDelimiterForNextFrame ? 0 : minDelimLength)); buffer.skipBytes(minFrameLength + minDelimLength);
int tooLongFrameLength = this.tooLongFrameLength; int tooLongFrameLength = this.tooLongFrameLength;
this.tooLongFrameLength = 0; this.tooLongFrameLength = 0;
@ -143,16 +142,10 @@ public class DelimiterBasedFrameDecoder extends ByteToMessageDecoder {
/*小于最小长度帧处理*/ /*小于最小长度帧处理*/
if (minFrameLength > maxFrameLength) { if (minFrameLength > maxFrameLength) {
//放弃读取帧 //放弃读取帧
buffer.skipBytes(minFrameLength + (keepDelimiterForNextFrame ? 0 : minDelimLength)); buffer.skipBytes(minFrameLength + minDelimLength);
fail(minFrameLength); fail(minFrameLength);
return null; return null;
} }
if (keepDelimiterForNextFrame) {
frame = buffer.readRetainedSlice(minFrameLength);
return frame;
}
/*是否需要跳过某字节*/ /*是否需要跳过某字节*/
if (minDelim.strip) { if (minDelim.strip) {
//忽略长度等于0的报文 //忽略长度等于0的报文
@ -273,9 +266,6 @@ public class DelimiterBasedFrameDecoder extends ByteToMessageDecoder {
return -1; return -1;
} }
// Modbus RTU:为了避免把请求帧(常见固定8字节)误当响应帧(第3字节是byteCount)导致截断,
// 这里采用"候选长度 + CRC校验"方式确定帧边界,兼容:请求/回显/响应/异常响应。
// 异常响应:地址(1) + 功能码(1|0x80) + 异常码(1) + CRC(2) = 5字节 // 异常响应:地址(1) + 功能码(1|0x80) + 异常码(1) + CRC(2) = 5字节
if ((functionCode & 0x80) != 0) { if ((functionCode & 0x80) != 0) {
int len = 5; int len = 5;
@ -309,8 +299,7 @@ public class DelimiterBasedFrameDecoder extends ByteToMessageDecoder {
} }
int byteCount = buffer.getByte(readerIndex + 2) & 0xFF; int byteCount = buffer.getByte(readerIndex + 2) & 0xFF;
int respLen = 3 + byteCount + 2; int respLen = 3 + byteCount + 2;
log.debug("[{}] Modbus功能码{}: byteCount={}, 预期帧长={}, 可读={}", log.debug("[{}] Modbus功能码{}: byteCount={}, 预期帧长={}, 可读={}", deviceId, String.format("%02X", functionCode), byteCount, respLen, readable);
deviceId, String.format("%02X", functionCode), byteCount, respLen, readable);
if (respLen > maxFrameLength) { if (respLen > maxFrameLength) {
log.warn("[{}] Modbus帧长度{}超过最大限制{}", deviceId, respLen, maxFrameLength); log.warn("[{}] Modbus帧长度{}超过最大限制{}", deviceId, respLen, maxFrameLength);
return -1; return -1;
@ -368,13 +357,7 @@ public class DelimiterBasedFrameDecoder extends ByteToMessageDecoder {
byte[] crc = CRC16Utils.getCrc16Byte(data); // [low, high] byte[] crc = CRC16Utils.getCrc16Byte(data); // [low, high]
int crcLo = buffer.getByte(offset + len - 2) & 0xFF; int crcLo = buffer.getByte(offset + len - 2) & 0xFF;
int crcHi = buffer.getByte(offset + len - 1) & 0xFF; int crcHi = buffer.getByte(offset + len - 1) & 0xFF;
boolean valid = ((crc[0] & 0xFF) == crcLo) && ((crc[1] & 0xFF) == crcHi); return ((crc[0] & 0xFF) == crcLo) && ((crc[1] & 0xFF) == crcHi);
if (!valid && log.isDebugEnabled()) {
log.debug("[{}] CRC校验: 计算值={}{}, 实际值={}{}", deviceId,
String.format("%02X", crc[0] & 0xFF), String.format("%02X", crc[1] & 0xFF),
String.format("%02X", crcLo), String.format("%02X", crcHi));
}
return valid;
} }
private void fail(long frameLength) { private void fail(long frameLength) {

View File

@ -114,4 +114,10 @@ public class ThingsModelValueItem {
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date ts; private Date ts;
/**
* 创建时间(用于排序)
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
} }

View File

@ -599,6 +599,7 @@ public class ThingsModelServiceImpl implements IThingsModelService {
arrayObjectThingsModel.setIsReadonly(param.getIsReadonly()); arrayObjectThingsModel.setIsReadonly(param.getIsReadonly());
arrayObjectThingsModel.setDatatype(param.getDatatype()); arrayObjectThingsModel.setDatatype(param.getDatatype());
arrayObjectThingsModel.setModelOrder(thingsModel.getModelOrder()); arrayObjectThingsModel.setModelOrder(thingsModel.getModelOrder());
arrayObjectThingsModel.setCreateTime(thingsModel.getCreateTime());
resultList.add(arrayObjectThingsModel); resultList.add(arrayObjectThingsModel);
} }
} }
@ -617,6 +618,7 @@ public class ThingsModelServiceImpl implements IThingsModelService {
arrayThingsModel.setIsReadonly(thingsModel.getIsReadonly()); arrayThingsModel.setIsReadonly(thingsModel.getIsReadonly());
arrayThingsModel.setDatatype(datatype); arrayThingsModel.setDatatype(datatype);
arrayThingsModel.setModelOrder(thingsModel.getModelOrder()); arrayThingsModel.setModelOrder(thingsModel.getModelOrder());
arrayThingsModel.setCreateTime(thingsModel.getCreateTime());
resultList.add(arrayThingsModel); resultList.add(arrayThingsModel);
} }
} else if ("object".equals(datatype.getType())) { } else if ("object".equals(datatype.getType())) {
@ -630,6 +632,7 @@ public class ThingsModelServiceImpl implements IThingsModelService {
newObjectThingsModel.setIsReadonly(objectThingsModel.getIsReadonly()); newObjectThingsModel.setIsReadonly(objectThingsModel.getIsReadonly());
newObjectThingsModel.setDatatype(objectThingsModel.getDatatype()); newObjectThingsModel.setDatatype(objectThingsModel.getDatatype());
newObjectThingsModel.setModelOrder(thingsModel.getModelOrder()); newObjectThingsModel.setModelOrder(thingsModel.getModelOrder());
newObjectThingsModel.setCreateTime(thingsModel.getCreateTime());
resultList.add(newObjectThingsModel); resultList.add(newObjectThingsModel);
} }
} else { } else {
@ -642,6 +645,7 @@ public class ThingsModelServiceImpl implements IThingsModelService {
otherThingsModel.setIsReadonly(thingsModel.getIsReadonly()); otherThingsModel.setIsReadonly(thingsModel.getIsReadonly());
otherThingsModel.setDatatype(datatype); otherThingsModel.setDatatype(datatype);
otherThingsModel.setModelOrder(thingsModel.getModelOrder()); otherThingsModel.setModelOrder(thingsModel.getModelOrder());
otherThingsModel.setCreateTime(thingsModel.getCreateTime());
resultList.add(otherThingsModel); resultList.add(otherThingsModel);
} }
return resultList; return resultList;
@ -686,24 +690,31 @@ public class ThingsModelServiceImpl implements IThingsModelService {
if (null == productId) { if (null == productId) {
return new ArrayList<>(); return new ArrayList<>();
} }
// 1、获取产品物模型 // 排序器:按 modelOrder 降序,createTime 降序(与数据库 SQL 一致)
Comparator<ThingsModelDTO> modelComparator = Comparator
.comparing(ThingsModelDTO::getModelOrder, Comparator.reverseOrder())
.thenComparing(ThingsModelDTO::getCreateTime, Comparator.nullsLast(Comparator.reverseOrder()));
// 1、获取产品物模型并排序
Map<String, ThingsModelValueItem> map = itslCache.getCacheThMapByProductId(productId); Map<String, ThingsModelValueItem> map = itslCache.getCacheThMapByProductId(productId);
List<ThingsModelDTO> thingsModelDTOList = new ArrayList<>(); List<ThingsModelDTO> thingsModelDTOList = new ArrayList<>();
for (Map.Entry<String, ThingsModelValueItem> entry : map.entrySet()) { for (Map.Entry<String, ThingsModelValueItem> entry : map.entrySet()) {
ThingsModel thingsModel = this.propertyDtoChangeThingsModel(entry.getValue()); ThingsModel thingsModel = this.propertyDtoChangeThingsModel(entry.getValue());
thingsModelDTOList.addAll(this.changeObjectOrArray(thingsModel)); thingsModelDTOList.addAll(this.changeObjectOrArray(thingsModel));
} }
thingsModelDTOList.sort(modelComparator);
// 2. 获取设备级计算物模型并合并 // 2. 获取设备级计算物模型并合并
if (StringUtils.isNotEmpty(serialNumber)) { if (StringUtils.isNotEmpty(serialNumber)) {
Map<String, ThingsModelValueItem> deviceCalculatedMap = itslCache.getDeviceCalculatedModelMap(serialNumber); Map<String, ThingsModelValueItem> deviceCalculatedMap = itslCache.getDeviceCalculatedModelMap(serialNumber);
List<ThingsModelDTO> deviceModelList = new ArrayList<>();
for (Map.Entry<String, ThingsModelValueItem> entry : deviceCalculatedMap.entrySet()) { for (Map.Entry<String, ThingsModelValueItem> entry : deviceCalculatedMap.entrySet()) {
ThingsModel thingsModel = this.propertyDtoChangeThingsModel(entry.getValue()); ThingsModel thingsModel = this.propertyDtoChangeThingsModel(entry.getValue());
thingsModelDTOList.addAll(this.changeObjectOrArray(thingsModel)); deviceModelList.addAll(this.changeObjectOrArray(thingsModel));
} }
deviceModelList.sort(modelComparator);
thingsModelDTOList.addAll(deviceModelList);
} }
thingsModelDTOList.sort(Comparator.comparing(ThingsModelDTO::getModelOrder));
if (StringUtils.isEmpty(serialNumber)) { if (StringUtils.isEmpty(serialNumber)) {
return thingsModelDTOList; return thingsModelDTOList;
} }
@ -767,6 +778,7 @@ public class ThingsModelServiceImpl implements IThingsModelService {
thingsModel.setSpecs(JSON.toJSONString(valueItem.getDatatype())); thingsModel.setSpecs(JSON.toJSONString(valueItem.getDatatype()));
thingsModel.setType(valueItem.getType()); thingsModel.setType(valueItem.getType());
thingsModel.setModelId(valueItem.getModelId()); thingsModel.setModelId(valueItem.getModelId());
thingsModel.setCreateTime(valueItem.getCreateTime());
return thingsModel; return thingsModel;
} }

14
vue/package-lock.json generated
View File

@ -5751,6 +5751,15 @@
"zrender": "5.6.1" "zrender": "5.6.1"
} }
}, },
"echarts-extension-leaflet": {
"version": "1.2.2",
"resolved": "https://registry.npmmirror.com/echarts-extension-leaflet/-/echarts-extension-leaflet-1.2.2.tgz",
"integrity": "sha512-EKZmIQDRqxnf6DtYWqujn2PRqHmEAq3yr0gsZAwmOT9rSy0Rwt9FlXKPjK3d+EVLqT6se6IQweyJTsqMg537Gg==",
"requires": {
"echarts": "^5.5.0",
"leaflet": "^1.9.4"
}
},
"echarts-liquidfill": { "echarts-liquidfill": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmmirror.com/echarts-liquidfill/-/echarts-liquidfill-3.1.0.tgz", "resolved": "https://registry.npmmirror.com/echarts-liquidfill/-/echarts-liquidfill-3.1.0.tgz",
@ -8974,6 +8983,11 @@
"launch-editor": "^2.12.0" "launch-editor": "^2.12.0"
} }
}, },
"leaflet": {
"version": "1.9.4",
"resolved": "https://registry.npmmirror.com/leaflet/-/leaflet-1.9.4.tgz",
"integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA=="
},
"leven": { "leven": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmmirror.com/leven/-/leven-2.1.0.tgz", "resolved": "https://registry.npmmirror.com/leven/-/leven-2.1.0.tgz",

View File

@ -46,6 +46,7 @@
"codemirror": "^5.65.2", "codemirror": "^5.65.2",
"core-js": "3.25.3", "core-js": "3.25.3",
"echarts": "^5.4.0", "echarts": "^5.4.0",
"echarts-extension-leaflet": "^1.2.2",
"echarts-liquidfill": "^3.0.6", "echarts-liquidfill": "^3.0.6",
"element-china-area-data": "^4.1.1", "element-china-area-data": "^4.1.1",
"element-ui": "2.15.10", "element-ui": "2.15.10",

View File

@ -1,28 +1,66 @@
export function loadBMap() { // 使用OpenStreetMap和Leaflet替代百度地图
export function loadMap() {
return new Promise(function(resolve, reject) { return new Promise(function(resolve, reject) {
if (typeof BMap !== 'undefined') { // eslint-disable-next-line no-undef
resolve(BMap) if (typeof L !== 'undefined') {
return true // eslint-disable-next-line no-undef
resolve(L);
return true;
} }
window.onBMapCallback = function() {
resolve(BMap) // 加载Leaflet CSS
if (!document.querySelector('link[href*="leaflet.css"]')) {
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.css';
link.integrity = 'sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=';
link.crossOrigin = '';
document.head.appendChild(link);
} }
// 使用https协议需要添加一下meta标签
var protocolStr = document.location.protocol; // 加载Leaflet JS
if(protocolStr == "https:") const script = document.createElement('script');
{ script.type = 'text/javascript';
let meta = document.createElement('meta') script.src = 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.js';
meta.httpEquiv = 'Content-Security-Policy' script.integrity = 'sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=';
meta.content ='upgrade-insecure-requests' script.crossOrigin = '';
meta.onerror = reject script.onload = function() {
document.head.appendChild(meta) // eslint-disable-next-line no-undef
} resolve(L);
// 引入百度地图 };
let script = document.createElement('script') script.onerror = reject;
script.type = 'text/javascript' document.head.appendChild(script);
script.src = });
'http://api.map.baidu.com/api?v=2.0&ak='+ process.env.VUE_APP_BAI_DU_AK +'&s=1&__ec_v__=20190126&callback=onBMapCallback'
script.onerror = reject
document.head.appendChild(script)
})
} }
// export function loadBMap() {
// return new Promise(function(resolve, reject) {
// if (typeof BMap !== 'undefined') {
// resolve(BMap)
// return true
// }
// window.onBMapCallback = function() {
// resolve(BMap)
// }
// // 使用https协议需要添加一下meta标签
// var protocolStr = document.location.protocol;
// if(protocolStr == "https:")
// {
// let meta = document.createElement('meta')
// meta.httpEquiv = 'Content-Security-Policy'
// meta.content ='upgrade-insecure-requests'
// meta.onerror = reject
// document.head.appendChild(meta)
// }
// // 引入百度地图
// let script = document.createElement('script')
// script.type = 'text/javascript'
// script.src =
// 'http://api.map.baidu.com/api?v=2.0&ak='+ process.env.VUE_APP_BAI_DU_AK +'&s=1&__ec_v__=20190126&callback=onBMapCallback'
// script.onerror = reject
// document.head.appendChild(script)
// })
// }

View File

@ -15,7 +15,7 @@
<script> <script>
import { import {
loadBMap loadMap
} from '@/utils/map.js' } from '@/utils/map.js'
//安装的是echarts完整包,里面包含百度地图扩展,路径为 echarts/extension/bmap/bmap.js,将其引入 //安装的是echarts完整包,里面包含百度地图扩展,路径为 echarts/extension/bmap/bmap.js,将其引入
//ECharts的百度地图扩展,可以在百度地图上展现点图,线图,热力图等可视化 //ECharts的百度地图扩展,可以在百度地图上展现点图,线图,热力图等可视化
@ -65,7 +65,7 @@ export default {
}, },
/**加载地图*/ /**加载地图*/
loadMap() { loadMap() {
loadBMap().then(() => { loadMap().then(() => {
this.getmap(); this.getmap();
}); });
}, },

View File

@ -319,10 +319,9 @@ import axios from 'axios';
import { getDeviceStatistic } from '@/api/iot/device'; import { getDeviceStatistic } from '@/api/iot/device';
import { listNotice, getNotice } from '@/api/system/notice'; import { listNotice, getNotice } from '@/api/system/notice';
import CountTo from 'vue-count-to'; import CountTo from 'vue-count-to';
import { loadBMap } from '@/utils/map.js'; import 'leaflet/dist/leaflet.css';
//安装的是echarts完整包,里面包含百度地图扩展,路径为 echarts/extension/bmap/bmap.js,将其引入 import 'echarts-extension-leaflet';
//ECharts的百度地图扩展,可以在百度地图上展现点图,线图,热力图等可视化 //import coordtransform from 'coordtransform';
// 注意:bmap扩展需要在BMap加载后再引入
import { getServer } from '@/api/monitor/server'; import { getServer } from '@/api/monitor/server';
import { listAllDeviceShort } from '@/api/iot/device'; import { listAllDeviceShort } from '@/api/iot/device';
import 'echarts-liquidfill'; import 'echarts-liquidfill';
@ -1157,23 +1156,10 @@ export default {
/**加载地图*/ /**加载地图*/
loadMap() { loadMap() {
this.$nextTick(() => { this.$nextTick(() => {
loadBMap().then(() => { // 使用 echarts-extension-leaflet,已在 import 时加载
if (typeof BMap === 'undefined') {
this.$message.error('百度地图API未正确加载');
return;
}
// 确保bmap扩展已加载
try {
require('echarts/extension/bmap/bmap');
} catch (e) {
// 忽略扩展加载错误
}
setTimeout(() => { setTimeout(() => {
this.getmap(); this.getmap();
}, 500); }, 300);
}).catch((error) => {
this.$message.error('百度地图加载失败,请检查API Key配置');
});
}); });
}, },
/** 查询服务器信息 */ /** 查询服务器信息 */
@ -1229,13 +1215,14 @@ export default {
/** 地图 */ /** 地图 */
getmap() { getmap() {
if (typeof BMap === 'undefined') { if (!this.$refs.map) {
this.$message.error('百度地图API未正确加载,请刷新页面重试');
return; return;
} }
if (!this.$refs.map) { // 销毁旧的ECharts实例,避免多个实例叠加
return; var existingChart = this.$echarts.getInstanceByDom(this.$refs.map);
if (existingChart) {
existingChart.dispose();
} }
var myChart = this.$echarts.init(this.$refs.map); var myChart = this.$echarts.init(this.$refs.map);
@ -1258,8 +1245,14 @@ export default {
let convertData = function (data, status) { let convertData = function (data, status) {
var res = []; var res = [];
for (var i = 0; i < data.length; i++) { for (var i = 0; i < data.length; i++) {
var geoCoord = [data[i].longitude, data[i].latitude]; // 验证经纬度有效性
if (geoCoord && data[i].status == status) { var lng = parseFloat(data[i].longitude);
var lat = parseFloat(data[i].latitude);
if (isNaN(lng) || isNaN(lat) || lng === 0 || lat === 0) {
continue;
}
var geoCoord = [lng, lat];
if (data[i].status == status) {
res.push({ res.push({
name: data[i].deviceName, name: data[i].deviceName,
value: geoCoord, value: geoCoord,
@ -1320,14 +1313,9 @@ export default {
return { center: [centerLng, centerLat], zoom: zoom }; return { center: [centerLng, centerLat], zoom: zoom };
}; };
// 使用长沙作为默认中心点,如果有设备数据则使用计算的中心点 // 使用中国长沙作为地图中心点
let mapConfig = { center: [112.867037, 28.11857], zoom: 12 }; // 长沙坐标:经度 112.94, 纬度 28.23
if (this.deviceList && this.deviceList.length > 0) { let mapConfig = { center: [112.94, 28.23], zoom: 5 };
const calculated = calculateMapCenter(this.deviceList);
if (calculated.center && calculated.center[0] > 0) {
mapConfig = calculated;
}
}
option = { option = {
title: { title: {
@ -1382,21 +1370,27 @@ export default {
return htmlStr; return htmlStr;
}, },
}, },
bmap: { leaflet: {
center: mapConfig.center, // echarts-extension-leaflet center 使用 [lng, lat] 格式
zoom: mapConfig.zoom, // 长沙坐标:经度 112.94, 纬度 28.23
center: [112.94, 28.23],
zoom: 5,
roam: true, roam: true,
// 确保地图正确初始化 tiles: [
enableMapClick: true, {
// 暂时不使用自定义样式,确保地图能正常显示 // 使用高德地图瓦片(国内访问稳定)
// mapStyle: { urlTemplate: 'https://webrd0{s}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}',
// styleJson: [...] options: {
// }, subdomains: ['1', '2', '3', '4'],
attribution: '&copy; 高德地图',
},
},
],
}, },
series: [ series: [
{ {
type: 'scatter', type: 'scatter',
coordinateSystem: 'bmap', coordinateSystem: 'leaflet',
data: convertData(this.deviceList, 1), data: convertData(this.deviceList, 1),
symbolSize: 15, symbolSize: 15,
itemStyle: { itemStyle: {
@ -1405,7 +1399,7 @@ export default {
}, },
{ {
type: 'scatter', type: 'scatter',
coordinateSystem: 'bmap', coordinateSystem: 'leaflet',
data: convertData(this.deviceList, 2), data: convertData(this.deviceList, 2),
symbolSize: 15, symbolSize: 15,
itemStyle: { itemStyle: {
@ -1414,7 +1408,7 @@ export default {
}, },
{ {
type: 'scatter', type: 'scatter',
coordinateSystem: 'bmap', coordinateSystem: 'leaflet',
data: convertData(this.deviceList, 4), data: convertData(this.deviceList, 4),
symbolSize: 15, symbolSize: 15,
itemStyle: { itemStyle: {
@ -1423,7 +1417,7 @@ export default {
}, },
{ {
type: 'effectScatter', type: 'effectScatter',
coordinateSystem: 'bmap', coordinateSystem: 'leaflet',
data: convertData(this.deviceList, 3), data: convertData(this.deviceList, 3),
symbolSize: 15, symbolSize: 15,
showEffectOn: 'render', showEffectOn: 'render',
@ -1465,35 +1459,10 @@ export default {
// 设置地图配置 // 设置地图配置
myChart.setOption(option, true); myChart.setOption(option, true);
// 等待地图初始化后调整大小和刷新 // 等待地图初始化完成
setTimeout(() => { this.$nextTick(() => {
myChart.resize(); myChart.resize();
});
const bmapModel = myChart.getModel().getComponent('bmap');
if (bmapModel && bmapModel.getBMap) {
const bmapInstance = bmapModel.getBMap();
if (bmapInstance) {
// 启用地图功能
bmapInstance.enableScrollWheelZoom(true);
bmapInstance.enableDoubleClickZoom(true);
bmapInstance.enableDragging(true);
// 设置中心点和缩放级别
const centerPoint = new BMap.Point(mapConfig.center[0], mapConfig.center[1]);
bmapInstance.setCenter(centerPoint);
bmapInstance.setZoom(mapConfig.zoom);
// 刷新地图确保瓦片加载
setTimeout(() => {
myChart.resize();
bmapInstance.reset();
bmapInstance.setCenter(centerPoint);
bmapInstance.setZoom(mapConfig.zoom);
myChart.resize();
}, 500);
}
}
}, 800);
} catch (error) { } catch (error) {
this.$message.error('地图初始化失败: ' + error.message); this.$message.error('地图初始化失败: ' + error.message);
} }

View File

@ -1,6 +1,7 @@
<template> <template>
<el-card style="margin: 6px; padding-bottom: 100px"> <el-card style="margin: 6px; padding-bottom: 100px">
<el-tabs id="deviceDetailTab" v-model="activeName" tab-position="left" @tab-click="tabChange" style="padding: 10px; min-height: 400px" lazy> <el-tabs id="deviceDetailTab" v-model="activeName" tab-position="left" @tab-click="tabChange"
style="padding: 10px; min-height: 400px" lazy>
<el-tab-pane name="basic"> <el-tab-pane name="basic">
<span slot="label">{{ $t('device.device-edit.148398-0') }}</span> <span slot="label">{{ $t('device.device-edit.148398-0') }}</span>
<el-form ref="form" :model="form" :rules="rules" label-width="100px"> <el-form ref="form" :model="form" :rules="rules" label-width="100px">
@ -8,7 +9,9 @@
<el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="8"> <el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="8">
<el-form-item :label="$t('device.device-edit.148398-1')" prop="deviceName"> <el-form-item :label="$t('device.device-edit.148398-1')" prop="deviceName">
<el-input v-model="form.deviceName" :placeholder="$t('device.device-edit.148398-2')"> <el-input v-model="form.deviceName" :placeholder="$t('device.device-edit.148398-2')">
<el-button slot="append" @click="openSummaryDialog" v-if="form.deviceId != 0">{{ $t('device.device-edit.148398-3') }}</el-button> <el-button slot="append" @click="openSummaryDialog" v-if="form.deviceId != 0">
{{ $t('device.device-edit.148398-3') }}
</el-button>
</el-input> </el-input>
</el-form-item> </el-form-item>
<el-form-item label="" prop="productName"> <el-form-item label="" prop="productName">
@ -16,8 +19,11 @@
<span style="color: red">*</span> <span style="color: red">*</span>
{{ $t('device.device-edit.148398-4') }} {{ $t('device.device-edit.148398-4') }}
</template> </template>
<el-input readonly v-model="form.productName" :placeholder="$t('device.device-edit.148398-5')" :disabled="form.status != 1"> <el-input readonly v-model="form.productName" :placeholder="$t('device.device-edit.148398-5')"
<el-button slot="append" @click="selectProduct()" :disabled="form.status != 1">{{ $t('device.device-edit.148398-6') }}</el-button> :disabled="form.status != 1">
<el-button slot="append" @click="selectProduct()" :disabled="form.status != 1">
{{ $t('device.device-edit.148398-6') }}
</el-button>
</el-input> </el-input>
</el-form-item> </el-form-item>
<el-form-item label="" prop="serialNumber"> <el-form-item label="" prop="serialNumber">
@ -25,11 +31,14 @@
<span style="color: red">*</span> <span style="color: red">*</span>
{{ $t('device.device-edit.148398-7') }} {{ $t('device.device-edit.148398-7') }}
</template> </template>
<el-input v-model="form.serialNumber" :placeholder="$t('device.device-edit.148398-8')" :disabled="form.status != 1" maxlength="32"> <el-input v-model="form.serialNumber" :placeholder="$t('device.device-edit.148398-8')"
<el-button v-if="form.deviceType !== 3" slot="append" @click="generateNum" :loading="genDisabled" :disabled="form.status != 1" v-hasPermi="['iot:device:add']"> :disabled="form.status != 1" maxlength="32">
<el-button v-if="form.deviceType !== 3" slot="append" @click="generateNum" :loading="genDisabled"
:disabled="form.status != 1" v-hasPermi="['iot:device:add']">
{{ $t('device.device-edit.148398-9') }} {{ $t('device.device-edit.148398-9') }}
</el-button> </el-button>
<el-button v-if="form.deviceType === 3" slot="append" @click="genSipID()" :disabled="form.status != 1" v-hasPermi="['iot:device:add']"> <el-button v-if="form.deviceType === 3" slot="append" @click="genSipID()" :disabled="form.status != 1"
v-hasPermi="['iot:device:add']">
{{ $t('device.device-edit.148398-9') }} {{ $t('device.device-edit.148398-9') }}
</el-button> </el-button>
</el-input> </el-input>
@ -45,13 +54,15 @@
</template> </template>
</el-form-item> </el-form-item>
<el-form-item :label="$t('device.device-edit.148398-12')" prop="firmwareVersion"> <el-form-item :label="$t('device.device-edit.148398-12')" prop="firmwareVersion">
<el-input v-model="form.firmwareVersion" :placeholder="$t('device.device-edit.148398-13')" type="number" step="0.1" :disabled="form.status != 1 || form.deviceType === 3"> <el-input v-model="form.firmwareVersion" :placeholder="$t('device.device-edit.148398-13')" type="number"
step="0.1" :disabled="form.status != 1 || form.deviceType === 3">
<template slot="prepend">Version</template> <template slot="prepend">Version</template>
</el-input> </el-input>
</el-form-item> </el-form-item>
<!-- 设备影子 --> <!-- 设备影子 -->
<el-form-item :label="$t('device.device-edit.148398-15')" prop="isShadow"> <el-form-item :label="$t('device.device-edit.148398-15')" prop="isShadow">
<el-switch v-model="form.isShadow" active-text="" inactive-text="" :active-value="1" :inactive-value="0" :disabled="form.deviceType === 3"></el-switch> <el-switch v-model="form.isShadow" active-text="" inactive-text="" :active-value="1" :inactive-value="0"
:disabled="form.deviceType === 3"></el-switch>
</el-form-item> </el-form-item>
<el-form-item v-if="form.deviceType != 4" :label="$t('device.device-edit.148398-16')" prop="deviceStatus"> <el-form-item v-if="form.deviceType != 4" :label="$t('device.device-edit.148398-16')" prop="deviceStatus">
@ -66,49 +77,61 @@
></el-switch> ></el-switch>
</el-form-item> </el-form-item>
<el-form-item :label="$t('device.device-edit.148398-17')" prop="remark"> <el-form-item :label="$t('device.device-edit.148398-17')" prop="remark">
<el-input v-model="form.remark" type="textarea" :placeholder="$t('device.device-edit.148398-18')" rows="1" /> <el-input v-model="form.remark" type="textarea" :placeholder="$t('device.device-edit.148398-18')"
rows="1"/>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="8"> <el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="8">
<el-form-item :label="$t('device.device-edit.148398-19')" prop="locationWay"> <el-form-item :label="$t('device.device-edit.148398-19')" prop="locationWay">
<el-select v-model="form.locationWay" :placeholder="$t('device.device-edit.148398-20')" clearable size="small" style="width: 100%" :disabled="form.deviceType === 3"> <el-select v-model="form.locationWay" :placeholder="$t('device.device-edit.148398-20')" clearable
<el-option v-for="dict in dict.type.iot_location_way" :key="dict.value" :label="dict.label" :value="Number(dict.value)" /> size="small" style="width: 100%" :disabled="form.deviceType === 3">
<el-option v-for="dict in dict.type.iot_location_way" :key="dict.value" :label="dict.label"
:value="Number(dict.value)"/>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item :label="$t('device.device-edit.148398-21')" prop="longitude"> <el-form-item :label="$t('device.device-edit.148398-21')" prop="longitude">
<el-input v-model="form.longitude" :placeholder="$t('device.device-edit.148398-22')" type="number" :disabled="form.locationWay != 3"> <el-input v-model="form.longitude" :placeholder="$t('device.device-edit.148398-22')" type="number"
<el-link slot="append" :underline="false" href="https://api.map.baidu.com/lbsapi/getpoint/index.html" target="_blank" :disabled="form.locationWay != 3"> :disabled="form.locationWay != 3">
<el-link slot="append" :underline="false" href="https://api.map.baidu.com/lbsapi/getpoint/index.html"
target="_blank" :disabled="form.locationWay != 3">
{{ $t('device.device-edit.148398-23') }} {{ $t('device.device-edit.148398-23') }}
</el-link> </el-link>
</el-input> </el-input>
</el-form-item> </el-form-item>
<el-form-item :label="$t('device.device-edit.148398-24')" prop="latitude"> <el-form-item :label="$t('device.device-edit.148398-24')" prop="latitude">
<el-input v-model="form.latitude" :placeholder="$t('device.device-edit.148398-25')" type="number" :disabled="form.locationWay != 3"> <el-input v-model="form.latitude" :placeholder="$t('device.device-edit.148398-25')" type="number"
<el-link slot="append" :underline="false" href="https://api.map.baidu.com/lbsapi/getpoint/index.html" target="_blank" :disabled="form.locationWay != 3"> :disabled="form.locationWay != 3">
<el-link slot="append" :underline="false" href="https://api.map.baidu.com/lbsapi/getpoint/index.html"
target="_blank" :disabled="form.locationWay != 3">
{{ $t('device.device-edit.148398-23') }} {{ $t('device.device-edit.148398-23') }}
</el-link> </el-link>
</el-input> </el-input>
</el-form-item> </el-form-item>
<el-form-item :label="$t('device.device-edit.148398-26')" prop="networkAddress"> <el-form-item :label="$t('device.device-edit.148398-26')" prop="networkAddress">
<el-input v-model="form.networkAddress" :placeholder="$t('device.device-edit.148398-27')" :disabled="form.locationWay != 3" /> <el-input v-model="form.networkAddress" :placeholder="$t('device.device-edit.148398-27')"
:disabled="form.locationWay != 3"/>
</el-form-item> </el-form-item>
<el-form-item :label="$t('device.device-edit.148398-28')" prop="networkIp"> <el-form-item :label="$t('device.device-edit.148398-28')" prop="networkIp">
<el-input v-model="form.networkIp" :placeholder="$t('device.device-edit.148398-29')" disabled/> <el-input v-model="form.networkIp" :placeholder="$t('device.device-edit.148398-29')" disabled/>
</el-form-item> </el-form-item>
<el-form-item :label="$t('device.device-edit.148398-30')" prop="activeTime"> <el-form-item :label="$t('device.device-edit.148398-30')" prop="activeTime">
<el-date-picker clearable v-model="form.activeTime" type="date" value-format="yyyy-MM-dd" :placeholder="$t('device.device-edit.148398-31')" disabled style="width: 100%"></el-date-picker> <el-date-picker clearable v-model="form.activeTime" type="date" value-format="yyyy-MM-dd"
:placeholder="$t('device.device-edit.148398-31')" disabled
style="width: 100%"></el-date-picker>
</el-form-item> </el-form-item>
<el-form-item :label="$t('device.device-edit.148398-32')" prop="rssi"> <el-form-item :label="$t('device.device-edit.148398-32')" prop="rssi">
<el-input v-model="form.rssi" :placeholder="$t('device.device-edit.148398-33')" disabled/> <el-input v-model="form.rssi" :placeholder="$t('device.device-edit.148398-33')" disabled/>
</el-form-item> </el-form-item>
<el-form-item :label="$t('device.device-edit.148398-34')" prop="remark" v-if="form.deviceId != 0"> <el-form-item :label="$t('device.device-edit.148398-34')" prop="remark" v-if="form.deviceId != 0">
<dict-tag :options="dict.type.iot_device_status" :value="form.status" style="display: inline-block; margin-right: 8px" /> <dict-tag :options="dict.type.iot_device_status" :value="form.status"
style="display: inline-block; margin-right: 8px"/>
<el-button size="small" @click="handleViewMqtt()">{{ $t('device.device-edit.148398-35') }}</el-button> <el-button size="small" @click="handleViewMqtt()">{{ $t('device.device-edit.148398-35') }}</el-button>
<el-button size="small" @click="openCodeDialog()">{{ $t('device.device-edit.148398-36') }}</el-button> <el-button size="small" @click="openCodeDialog()">{{ $t('device.device-edit.148398-36') }}</el-button>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="8" v-if="form.deviceId != 0"> <el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="8" v-if="form.deviceId != 0">
<div style="border: 1px solid #dfe4ed; border-radius: 5px; padding: 5px; text-align: center; line-height: 400px"> <div
style="border: 1px solid #dfe4ed; border-radius: 5px; padding: 5px; text-align: center; line-height: 400px">
<div id="map" style="height: 435px; width: 100%">{{ $t('device.device-edit.148398-37') }}</div> <div id="map" style="height: 435px; width: 100%">{{ $t('device.device-edit.148398-37') }}</div>
</div> </div>
</el-col> </el-col>
@ -258,7 +281,9 @@
</el-tab-pane> --> </el-tab-pane> -->
<el-tab-pane name="deviceReturn" disabled> <el-tab-pane name="deviceReturn" disabled>
<span slot="label"> <span slot="label">
<el-button type="info" size="mini" @click="goBack()">{{ $t('device.device-edit.148398-53') }}</el-button> <el-button type="info" size="mini" @click="goBack()">{{
$t('device.device-edit.148398-53')
}}</el-button>
</span> </span>
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
@ -351,8 +376,15 @@ import deviceFuncLog from './device-functionlog';
import deviceSub from './device-sub'; import deviceSub from './device-sub';
import deviceThingsModel from './device-things-model.vue'; import deviceThingsModel from './device-things-model.vue';
import vueQr from 'vue-qr'; import vueQr from 'vue-qr';
import { loadBMap } from '@/utils/map.js'; import {loadMap} from '@/utils/map.js';
import { deviceSynchronization, getDevice, addDevice, updateDevice, generatorDeviceNum, getMqttConnect } from '@/api/iot/device'; import {
deviceSynchronization,
getDevice,
addDevice,
updateDevice,
generatorDeviceNum,
getMqttConnect
} from '@/api/iot/device';
import {getDeviceRunningStatus} from '@/api/iot/device'; import {getDeviceRunningStatus} from '@/api/iot/device';
import {cacheJsonThingsModel} from '@/api/iot/model'; import {cacheJsonThingsModel} from '@/api/iot/model';
import DeviceFunc from '@/views/iot/device/device-functionlog'; import DeviceFunc from '@/views/iot/device/device-functionlog';
@ -875,7 +907,7 @@ export default {
/**加载地图*/ /**加载地图*/
loadMap() { loadMap() {
this.$nextTick(() => { this.$nextTick(() => {
loadBMap().then(() => { loadMap().then(() => {
this.getmap(); this.getmap();
}); });
}); });
@ -954,7 +986,8 @@ export default {
//是否设备设置为禁用状态,则踢出设备 //是否设备设置为禁用状态,则踢出设备
if (this.form.status === 2) { if (this.form.status === 2) {
const params = {clientId: this.form.serialNumber}; const params = {clientId: this.form.serialNumber};
clientOut(params).then((res) => {}); clientOut(params).then((res) => {
});
} }
} }
}); });
@ -1033,7 +1066,8 @@ export default {
}, },
// 获取选中的用户 // 获取选中的用户
getUserData(user) {}, getUserData(user) {
},
/**关闭物模型 */ /**关闭物模型 */
openSummaryDialog() { openSummaryDialog() {
let json = { let json = {
@ -1088,21 +1122,44 @@ export default {
}, },
// 地图定位 // 地图定位
getmap() { getmap() {
this.map = new BMap.Map('map'); // 清除旧地图实例
let point = null; if (this.map) {
if (this.form.longitude != null && this.form.longitude != '' && this.form.latitude != null && this.form.latitude != '') { this.map.remove();
point = new BMap.Point(this.form.longitude, this.form.latitude); this.map = null;
} else {
point = new BMap.Point(116.404, 39.915);
} }
this.map.centerAndZoom(point, 19);
this.map.enableScrollWheelZoom(true); // 开启鼠标滚轮缩放
this.map.addControl(new BMap.NavigationControl());
// 标注设备位置 // 使用Leaflet和高德地图
this.mk = new BMap.Marker(point); let lat, lng;
this.map.addOverlay(this.mk); let parsedLat = parseFloat(this.form.latitude);
this.map.panTo(point); let parsedLng = parseFloat(this.form.longitude);
// 验证坐标有效性
if (!isNaN(parsedLat) && !isNaN(parsedLng) && parsedLat !== 0 && parsedLng !== 0) {
lat = parsedLat;
lng = parsedLng;
} else {
// 默认使用中国长沙坐标
lat = 28.23;
lng = 112.94;
}
// 创建地图
this.map = L.map('map').setView([lat, lng], 15);
// 添加高德地图图层(国内访问稳定)
L.tileLayer('https://webrd0{s}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}', {
subdomains: ['1', '2', '3', '4'],
attribution: '&copy; 高德地图',
maxZoom: 18
}).addTo(this.map);
// 添加标记点
this.mk = L.marker([lat, lng]).addTo(this.map);
// 添加popup提示
if (this.form.deviceName) {
this.mk.bindPopup(`<b>${this.form.deviceName}</b><br>${this.form.networkAddress || ''}`).openPopup();
}
}, },
// 生成随机字母和数字 // 生成随机字母和数字
generateNum() { generateNum() {

View File

@ -4,7 +4,8 @@
<el-col :span="5" :xs="24"> <el-col :span="5" :xs="24">
<el-card style="margin: 20px 0 20px 20px"> <el-card style="margin: 20px 0 20px 20px">
<div class="menu-wrap"> <div class="menu-wrap">
<el-input v-model="deptName" :placeholder="$t('device.device-linkage.188958-0')" clearable size="small" prefix-icon="el-icon-search" style="margin-bottom: 20px; margin-right: 10px" /> <el-input v-model="deptName" :placeholder="$t('device.device-linkage.188958-0')" clearable size="small"
prefix-icon="el-icon-search" style="margin-bottom: 20px; margin-right: 10px"/>
<el-tree <el-tree
:data="deptOptions" :data="deptOptions"
:props="defaultProps" :props="defaultProps"
@ -34,7 +35,8 @@
style="margin-bottom: 20px; margin-right: 10px" style="margin-bottom: 20px; margin-right: 10px"
@keydown.enter.native="getDeviceList" @keydown.enter.native="getDeviceList"
/> />
<el-menu class="menu-wrap" :default-active="deviceList[0] && deviceList[0].devcieId" :default-openeds="defaultOpeneds" @open="handleOpen" style="margin-right: 10px"> <el-menu class="menu-wrap" :default-active="deviceList[0] && deviceList[0].devcieId"
:default-openeds="defaultOpeneds" @open="handleOpen" style="margin-right: 10px">
<el-submenu index="1" style="margin-left: -5px"> <el-submenu index="1" style="margin-left: -5px">
<template slot="title"> <template slot="title">
<i class="el-icon-menu"></i> <i class="el-icon-menu"></i>
@ -60,7 +62,8 @@
<el-col :span="19" :xs="24"> <el-col :span="19" :xs="24">
<el-card style="margin: 20px 20px 20px 0"> <el-card style="margin: 20px 20px 20px 0">
<div class="head-container"> <div class="head-container">
<el-tabs v-model="activeName" tab-position="top" @tab-click="tabChange" style="min-height: 400px" v-if="devTotal > 0"> <el-tabs v-model="activeName" tab-position="top" @tab-click="tabChange" style="min-height: 400px"
v-if="devTotal > 0">
<el-tab-pane name="basic"> <el-tab-pane name="basic">
<span slot="label">{{ $t('device.device-edit.148398-0') }}</span> <span slot="label">{{ $t('device.device-edit.148398-0') }}</span>
<el-form ref="form" :model="form" :rules="rules" label-width="100px"> <el-form ref="form" :model="form" :rules="rules" label-width="100px">
@ -68,7 +71,9 @@
<el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="8"> <el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="8">
<el-form-item :label="$t('device.device-edit.148398-1')" prop="deviceName"> <el-form-item :label="$t('device.device-edit.148398-1')" prop="deviceName">
<el-input v-model="form.deviceName" :placeholder="$t('device.device-edit.148398-2')"> <el-input v-model="form.deviceName" :placeholder="$t('device.device-edit.148398-2')">
<el-button slot="append" @click="openSummaryDialog" v-if="form.deviceId != 0">{{ $t('device.device-edit.148398-3') }}</el-button> <el-button slot="append" @click="openSummaryDialog" v-if="form.deviceId != 0">
{{ $t('device.device-edit.148398-3') }}
</el-button>
</el-input> </el-input>
</el-form-item> </el-form-item>
<el-form-item label="" prop="productName"> <el-form-item label="" prop="productName">
@ -76,8 +81,12 @@
<span style="color: red">*</span> <span style="color: red">*</span>
{{ $t('device.device-edit.148398-4') }} {{ $t('device.device-edit.148398-4') }}
</template> </template>
<el-input readonly v-model="form.productName" :placeholder="$t('device.allot-import-dialog.060657-1')" disabled> <el-input readonly v-model="form.productName"
<el-button slot="append" @click="selectProduct()" disabled>{{ $t('device.device-edit.148398-6') }}</el-button> :placeholder="$t('device.allot-import-dialog.060657-1')" disabled>
<el-button slot="append" @click="selectProduct()" disabled>{{
$t('device.device-edit.148398-6')
}}
</el-button>
</el-input> </el-input>
</el-form-item> </el-form-item>
<el-form-item label="" prop="serialNumber"> <el-form-item label="" prop="serialNumber">
@ -85,11 +94,15 @@
<span style="color: red">*</span> <span style="color: red">*</span>
{{ $t('device.device-edit.148398-7') }} {{ $t('device.device-edit.148398-7') }}
</template> </template>
<el-input v-model="form.serialNumber" :placeholder="$t('device.device-edit.148398-8')" disabled maxlength="32"> <el-input v-model="form.serialNumber" :placeholder="$t('device.device-edit.148398-8')" disabled
<el-button v-if="form.deviceType !== 3" slot="append" @click="generateNum" :loading="genDisabled" disabled v-hasPermi="['iot:device:add']"> maxlength="32">
<el-button v-if="form.deviceType !== 3" slot="append" @click="generateNum"
:loading="genDisabled" disabled v-hasPermi="['iot:device:add']">
{{ $t('device.device-edit.148398-9') }} {{ $t('device.device-edit.148398-9') }}
</el-button> </el-button>
<el-button v-if="form.deviceType === 3" slot="append" @click="genSipID()" disabled v-hasPermi="['iot:device:add']">{{ $t('device.device-edit.148398-9') }}</el-button> <el-button v-if="form.deviceType === 3" slot="append" @click="genSipID()" disabled
v-hasPermi="['iot:device:add']">{{ $t('device.device-edit.148398-9') }}
</el-button>
</el-input> </el-input>
</el-form-item> </el-form-item>
<el-form-item v-if="openServerTip"> <el-form-item v-if="openServerTip">
@ -103,18 +116,22 @@
</template> </template>
</el-form-item> </el-form-item>
<el-form-item :label="$t('device.device-edit.148398-12')" prop="firmwareVersion"> <el-form-item :label="$t('device.device-edit.148398-12')" prop="firmwareVersion">
<el-input v-model="form.firmwareVersion" :placeholder="$t('device.device-edit.148398-13')" type="number" step="0.1" :disabled="form.status != 1 || form.deviceType === 3"> <el-input v-model="form.firmwareVersion" :placeholder="$t('device.device-edit.148398-13')"
type="number" step="0.1" :disabled="form.status != 1 || form.deviceType === 3">
<template slot="prepend">Version</template> <template slot="prepend">Version</template>
</el-input> </el-input>
</el-form-item> </el-form-item>
<el-form-item :label="$t('device.device-edit.148398-14')" prop="isSimulate"> <el-form-item :label="$t('device.device-edit.148398-14')" prop="isSimulate">
<el-switch v-model="form.isSimulate" active-text="" inactive-text="" :active-value="1" :inactive-value="0" disabled></el-switch> <el-switch v-model="form.isSimulate" active-text="" inactive-text="" :active-value="1"
:inactive-value="0" disabled></el-switch>
</el-form-item> </el-form-item>
<el-form-item :label="$t('device.device-edit.148398-15')" prop="isShadow"> <el-form-item :label="$t('device.device-edit.148398-15')" prop="isShadow">
<el-switch v-model="form.isShadow" active-text="" inactive-text="" :active-value="1" :inactive-value="0" disabled></el-switch> <el-switch v-model="form.isShadow" active-text="" inactive-text="" :active-value="1"
:inactive-value="0" disabled></el-switch>
</el-form-item> </el-form-item>
<el-form-item :label="$t('device.device-edit.148398-16')" prop="deviceStatus"> <el-form-item :label="$t('device.device-edit.148398-16')" prop="deviceStatus">
<el-switch v-model="deviceStatus" active-text="" inactive-text="" disabled :active-value="1" :inactive-value="0" active-color="#F56C6C"></el-switch> <el-switch v-model="deviceStatus" active-text="" inactive-text="" disabled :active-value="1"
:inactive-value="0" active-color="#F56C6C"></el-switch>
</el-form-item> </el-form-item>
<el-form-item :label="$t('device.device-edit.148398-17')" prop="remark"> <el-form-item :label="$t('device.device-edit.148398-17')" prop="remark">
<el-input v-model="form.remark" type="textarea" :placeholder="$t('plzInput')" rows="1"/> <el-input v-model="form.remark" type="textarea" :placeholder="$t('plzInput')" rows="1"/>
@ -122,26 +139,35 @@
</el-col> </el-col>
<el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="8"> <el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="8">
<el-form-item :label="$t('device.device-edit.148398-19')" prop="locationWay"> <el-form-item :label="$t('device.device-edit.148398-19')" prop="locationWay">
<el-select v-model="form.locationWay" :placeholder="$t('device.device-linkage.188958-25')" clearable size="small" style="width: 100%" :disabled="form.deviceType === 3"> <el-select v-model="form.locationWay" :placeholder="$t('device.device-linkage.188958-25')"
<el-option v-for="dict in dict.type.iot_location_way" :key="dict.value" :label="dict.label" :value="Number(dict.value)" /> clearable size="small" style="width: 100%" :disabled="form.deviceType === 3">
<el-option v-for="dict in dict.type.iot_location_way" :key="dict.value" :label="dict.label"
:value="Number(dict.value)"/>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item :label="$t('device.device-linkage.188958-26')" prop="longitude"> <el-form-item :label="$t('device.device-linkage.188958-26')" prop="longitude">
<el-input v-model="form.longitude" :placeholder="$t('device.device-linkage.188958-27')" type="number" disabled> <el-input v-model="form.longitude" :placeholder="$t('device.device-linkage.188958-27')"
<el-link slot="append" :underline="false" href="https://api.map.baidu.com/lbsapi/getpoint/index.html" target="_blank" :disabled="form.locationWay != 3"> type="number" disabled>
<el-link slot="append" :underline="false"
href="https://api.map.baidu.com/lbsapi/getpoint/index.html" target="_blank"
:disabled="form.locationWay != 3">
{{ $t('device.device-linkage.188958-28') }} {{ $t('device.device-linkage.188958-28') }}
</el-link> </el-link>
</el-input> </el-input>
</el-form-item> </el-form-item>
<el-form-item :label="$t('device.device-linkage.188958-29')" prop="latitude"> <el-form-item :label="$t('device.device-linkage.188958-29')" prop="latitude">
<el-input v-model="form.latitude" :placeholder="$t('device.device-linkage.188958-30')" type="number" disabled> <el-input v-model="form.latitude" :placeholder="$t('device.device-linkage.188958-30')"
<el-link slot="append" :underline="false" href="https://api.map.baidu.com/lbsapi/getpoint/index.html" target="_blank" :disabled="form.locationWay != 3"> type="number" disabled>
<el-link slot="append" :underline="false"
href="https://api.map.baidu.com/lbsapi/getpoint/index.html" target="_blank"
:disabled="form.locationWay != 3">
{{ $t('device.device-linkage.188958-28') }} {{ $t('device.device-linkage.188958-28') }}
</el-link> </el-link>
</el-input> </el-input>
</el-form-item> </el-form-item>
<el-form-item :label="$t('device.device-linkage.188958-31')" prop="networkAddress"> <el-form-item :label="$t('device.device-linkage.188958-31')" prop="networkAddress">
<el-input v-model="form.networkAddress" :placeholder="$t('device.device-linkage.188958-32')" disabled /> <el-input v-model="form.networkAddress" :placeholder="$t('device.device-linkage.188958-32')"
disabled/>
</el-form-item> </el-form-item>
<el-form-item :label="$t('device.device-linkage.188958-33')" prop="networkIp"> <el-form-item :label="$t('device.device-linkage.188958-33')" prop="networkIp">
<el-input v-model="form.networkIp" :placeholder="$t('device.device-linkage.188958-34')" disabled/> <el-input v-model="form.networkIp" :placeholder="$t('device.device-linkage.188958-34')" disabled/>
@ -160,14 +186,23 @@
<el-form-item :label="$t('device.device-linkage.188958-37')" prop="rssi"> <el-form-item :label="$t('device.device-linkage.188958-37')" prop="rssi">
<el-input v-model="form.rssi" :placeholder="$t('device.device-linkage.188958-38')" disabled/> <el-input v-model="form.rssi" :placeholder="$t('device.device-linkage.188958-38')" disabled/>
</el-form-item> </el-form-item>
<el-form-item :label="$t('device.device-linkage.188958-39')" prop="remark" v-if="form.deviceId != 0"> <el-form-item :label="$t('device.device-linkage.188958-39')" prop="remark"
<dict-tag :options="dict.type.iot_device_status" :value="form.status" style="display: inline-block; margin-right: 8px" /> v-if="form.deviceId != 0">
<el-button size="small" @click="handleViewMqtt()">{{ $t('device.device-linkage.188958-40') }}</el-button> <dict-tag :options="dict.type.iot_device_status" :value="form.status"
<el-button size="small" @click="openCodeDialog()">{{ $t('device.device-linkage.188958-41') }}</el-button> style="display: inline-block; margin-right: 8px"/>
<el-button size="small" @click="handleViewMqtt()">{{
$t('device.device-linkage.188958-40')
}}
</el-button>
<el-button size="small" @click="openCodeDialog()">{{
$t('device.device-linkage.188958-41')
}}
</el-button>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="8" v-if="form.deviceId != 0"> <el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="8" v-if="form.deviceId != 0">
<div style="border: 1px solid #dfe4ed; border-radius: 5px; padding: 5px; text-align: center; line-height: 400px"> <div
style="border: 1px solid #dfe4ed; border-radius: 5px; padding: 5px; text-align: center; line-height: 400px">
<div id="map" style="height: 435px; width: 100%">{{ $t('device.device-linkage.188958-42') }}</div> <div id="map" style="height: 435px; width: 100%">{{ $t('device.device-linkage.188958-42') }}</div>
</div> </div>
</el-col> </el-col>
@ -230,7 +265,9 @@
</el-tab-pane> </el-tab-pane>
<el-tab-pane name="alertUser" :disabled="form.deviceId == 0" v-if="form.deviceType !== 3"> <el-tab-pane name="alertUser" :disabled="form.deviceId == 0" v-if="form.deviceType !== 3">
<span slot="label" v-hasPermi="['iot:device:alert:user:list']">{{ $t('device.device-edit.148398-80') }}</span> <span slot="label" v-hasPermi="['iot:device:alert:user:list']">{{
$t('device.device-edit.148398-80')
}}</span>
<alert-user ref="alertUser" :device="form"/> <alert-user ref="alertUser" :device="form"/>
</el-tab-pane> </el-tab-pane>
<el-tab-pane name="deviceAlert" :disabled="form.deviceId == 0"> <el-tab-pane name="deviceAlert" :disabled="form.deviceId == 0">
@ -269,7 +306,8 @@
<instruction-parsing ref="instructionParsing" :device="form"/> <instruction-parsing ref="instructionParsing" :device="form"/>
</el-tab-pane> </el-tab-pane>
<el-tab-pane name="scada" :disabled="form.deviceId == 0" v-if="form.deviceType !== 3 && isShowScada == true" lazy> <el-tab-pane name="scada" :disabled="form.deviceId == 0" v-if="form.deviceType !== 3 && isShowScada == true"
lazy>
<span slot="label">{{ $t('device.device-edit.148398-73') }}</span> <span slot="label">{{ $t('device.device-edit.148398-73') }}</span>
<device-scada ref="deviceScada" :device="form"/> <device-scada ref="deviceScada" :device="form"/>
</el-tab-pane> </el-tab-pane>
@ -294,7 +332,8 @@
</el-tabs> </el-tabs>
<el-empty :description="$t('device.index.105953-41')" v-if="devTotal == 0"></el-empty> <el-empty :description="$t('device.index.105953-41')" v-if="devTotal == 0"></el-empty>
<!-- 设备配置JSON --> <!-- 设备配置JSON -->
<el-dialog :title="$t('device.device-linkage.188958-54')" :visible.sync="openSummary" width="700px" append-to-body> <el-dialog :title="$t('device.device-linkage.188958-54')" :visible.sync="openSummary" width="700px"
append-to-body>
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :span="14"> <el-col :span="14">
<div style="border: 1px solid #ccc; margin-top: -15px; height: 350px; width: 360px; overflow: scroll"> <div style="border: 1px solid #ccc; margin-top: -15px; height: 350px; width: 360px; overflow: scroll">
@ -304,7 +343,8 @@
</div> </div>
</el-col> </el-col>
<el-col :span="10"> <el-col :span="10">
<div style="border: 1px solid #ccc; width: 200px; text-align: center; margin-left: 20px; margin-top: -10px"> <div
style="border: 1px solid #ccc; width: 200px; text-align: center; margin-left: 20px; margin-top: -10px">
<vue-qr :text="qrText" :size="200"></vue-qr> <vue-qr :text="qrText" :size="200"></vue-qr>
<div style="padding-bottom: 10px">{{ $t('device.device-linkage.188958-56') }}</div> <div style="padding-bottom: 10px">{{ $t('device.device-linkage.188958-56') }}</div>
</div> </div>
@ -321,7 +361,8 @@
<div style="padding-bottom: 10px">{{ $t('device.device-linkage.188958-56') }}</div> <div style="padding-bottom: 10px">{{ $t('device.device-linkage.188958-56') }}</div>
</div> </div>
</el-dialog> </el-dialog>
<el-dialog :title="$t('device.device-linkage.188958-58')" :visible.sync="openViewMqtt" width="600px" append-to-body> <el-dialog :title="$t('device.device-linkage.188958-58')" :visible.sync="openViewMqtt" width="600px"
append-to-body>
<el-form ref="listQuery" :model="listQuery" :rules="rules" label-width="150px"> <el-form ref="listQuery" :model="listQuery" :rules="rules" label-width="150px">
<el-form-item label="clientId" prop="clientId"> <el-form-item label="clientId" prop="clientId">
<el-input v-model="listQuery.clientId" disabled style="width: 80%"/> <el-input v-model="listQuery.clientId" disabled style="width: 80%"/>
@ -337,7 +378,10 @@
</el-form-item> </el-form-item>
</el-form> </el-form>
<div slot="footer" class="dialog-footer"> <div slot="footer" class="dialog-footer">
<el-button class="btns" type="primary" @click="doCopy(2)">{{ $t('device.device-linkage.188958-59') }}</el-button> <el-button class="btns" type="primary" @click="doCopy(2)">{{
$t('device.device-linkage.188958-59')
}}
</el-button>
<el-button @click="closeSummaryDialog">{{ $t('device.device-linkage.188958-57') }}</el-button> <el-button @click="closeSummaryDialog">{{ $t('device.device-linkage.188958-57') }}</el-button>
</div> </div>
</el-dialog> </el-dialog>
@ -366,8 +410,15 @@ import deviceFuncLog from './device-functionlog';
import deviceSub from './device-sub'; import deviceSub from './device-sub';
import alertUser from './alert-user'; import alertUser from './alert-user';
import vueQr from 'vue-qr'; import vueQr from 'vue-qr';
import { loadBMap } from '@/utils/map.js'; import {loadMap} from '@/utils/map.js';
import { deviceSynchronization, getDevice, addDevice, updateDevice, generatorDeviceNum, getMqttConnect } from '@/api/iot/device'; import {
deviceSynchronization,
getDevice,
addDevice,
updateDevice,
generatorDeviceNum,
getMqttConnect
} from '@/api/iot/device';
import {getDeviceUser} from '@/api/iot/deviceuser'; import {getDeviceUser} from '@/api/iot/deviceuser';
import {getUserId} from '@/utils/auth'; import {getUserId} from '@/utils/auth';
import {getDeviceRunningStatus} from '@/api/iot/device'; import {getDeviceRunningStatus} from '@/api/iot/device';
@ -980,7 +1031,7 @@ export default {
/**加载地图*/ /**加载地图*/
loadMap() { loadMap() {
this.$nextTick(() => { this.$nextTick(() => {
loadBMap().then(() => { loadMap().then(() => {
this.getmap(); this.getmap();
}); });
}); });
@ -1057,7 +1108,8 @@ export default {
//是否设备设置为禁用状态,则踢出设备 //是否设备设置为禁用状态,则踢出设备
if (this.form.status === 2) { if (this.form.status === 2) {
const params = {clientId: this.form.serialNumber}; const params = {clientId: this.form.serialNumber};
clientOut(params).then((res) => {}); clientOut(params).then((res) => {
});
} }
} }
}); });
@ -1145,7 +1197,8 @@ export default {
}); });
}, },
// 获取选中的用户 // 获取选中的用户
getUserData(user) {}, getUserData(user) {
},
/**关闭物模型 */ /**关闭物模型 */
openSummaryDialog() { openSummaryDialog() {
let json = { let json = {
@ -1185,20 +1238,40 @@ export default {
}, },
// 地图定位 // 地图定位
getmap() { getmap() {
this.map = new BMap.Map('map'); // 清除旧地图实例
let point = null; if (this.map) {
if (this.form.longitude != null && this.form.longitude != '' && this.form.latitude != null && this.form.latitude != '') { this.map.remove();
point = new BMap.Point(this.form.longitude, this.form.latitude); this.map = null;
} else { }
point = new BMap.Point(116.404, 39.915);
// 使用Leaflet和OpenStreetMap
let lat, lng;
if (this.form.latitude != null && this.form.latitude != '' && this.form.longitude != null && this.form.longitude != '') {
lat = parseFloat(this.form.latitude);
lng = parseFloat(this.form.longitude);
} else {
// 默认使用北京天安门坐标
lat = 39.915;
lng = 116.404;
}
// 创建地图
this.map = L.map('map').setView([lat, lng], 15);
// 添加高德地图图层(国内访问稳定)
L.tileLayer('https://webrd0{s}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}', {
subdomains: ['1', '2', '3', '4'],
attribution: '&copy; 高德地图',
maxZoom: 18
}).addTo(this.map);
// 添加标记点
this.mk = L.marker([lat, lng]).addTo(this.map);
// 添加popup提示
if (this.form.deviceName) {
this.mk.bindPopup(`<b>${this.form.deviceName}</b><br>${this.form.networkAddress || ''}`).openPopup();
} }
this.map.centerAndZoom(point, 19);
this.map.enableScrollWheelZoom(true); // 开启鼠标滚轮缩放
this.map.addControl(new BMap.NavigationControl());
// 标注设备位置
this.mk = new BMap.Marker(point);
this.map.addOverlay(this.mk);
this.map.panTo(point);
}, },
// 生成随机字母和数字 // 生成随机字母和数字
generateNum() { generateNum() {
@ -1256,6 +1329,7 @@ export default {
margin-top: -10px; margin-top: -10px;
margin-left: -15px; margin-left: -15px;
} }
.tabs-container { .tabs-container {
display: flex; display: flex;
justify-content: space-around; justify-content: space-around;

View File

@ -31,19 +31,25 @@
</div> </div>
<div class="item-content"> <div class="item-content">
<div class="content-value"> <div class="content-value">
<!-- 根据format选择显示HEX或ASCII格式 --> <!-- 自动识别并展示所有可用格式 -->
<div v-if="format === 'Hex' && item.value" class="data-format-item"> <!-- JSON格式优先展示(如果是有效JSON) -->
<span class="format-label">HEX:</span> <div v-if="isValidJson(item.valueAscii || item.value)" class="data-format-item json-item">
<span class="hex-value">{{ item.value }}</span>
</div>
<div v-if="format === 'ASCII' && item.valueAscii" class="data-format-item ascii-item">
<span class="format-label">ASCII:</span>
<pre class="ascii-value">{{ item.valueAscii }}</pre>
</div>
<div v-if="format === 'JSON'" class="data-format-item">
<span class="format-label">JSON:</span> <span class="format-label">JSON:</span>
<pre class="json-value">{{ formatJsonValue(item.value, item.valueAscii) }}</pre> <pre class="json-value">{{ formatJsonValue(item.value, item.valueAscii) }}</pre>
</div> </div>
<!-- 非JSON时展示HEX和ASCII -->
<template v-else>
<!-- HEX格式 -->
<div v-if="item.value" class="data-format-item">
<span class="format-label">HEX:</span>
<span class="hex-value">{{ item.value }}</span>
</div>
<!-- ASCII格式(如果有可读内容) -->
<div v-if="item.valueAscii && hasReadableAscii(item.valueAscii)" class="data-format-item ascii-item">
<span class="format-label">ASCII:</span>
<pre class="ascii-value">{{ item.valueAscii }}</pre>
</div>
</template>
</div> </div>
</div> </div>
<div class="analysis-data" v-if="item.type != 0 && item.analysis"> <div class="analysis-data" v-if="item.type != 0 && item.analysis">
@ -564,6 +570,24 @@ export default {
} }
// 如果都不是JSON,返回提示信息 // 如果都不是JSON,返回提示信息
return '数据不是有效的JSON格式'; return '数据不是有效的JSON格式';
},
/**判断是否为有效的JSON */
isValidJson(value) {
if (!value) return false;
try {
const parsed = JSON.parse(value);
// 确保是对象或数组,而不是简单值
return typeof parsed === 'object' && parsed !== null;
} catch (e) {
return false;
}
},
/**判断ASCII是否包含可读内容 */
hasReadableAscii(asciiValue) {
if (!asciiValue) return false;
// 检查是否有可打印字符(排除纯控制字符)
const printableRegex = /[\x20-\x7E]/;
return printableRegex.test(asciiValue) && asciiValue.trim().length > 0;
}, },
/**打开编辑框 */ /**打开编辑框 */
openEdit() { openEdit() {
@ -990,6 +1014,11 @@ $right-btn-color: #1890ff;
} }
.right { .right {
display: flex;
flex-direction: column;
// 高度 = 头部36px + 内容700px + 底部64px
height: 800px;
.right-head { .right-head {
font-size: 14px; font-size: 14px;
text-align: center; text-align: center;
@ -997,19 +1026,28 @@ $right-btn-color: #1890ff;
justify-content: center; justify-content: center;
} }
// 覆盖通用 .content 样式
.content.beautify-scroll-def {
flex: 1;
height: auto;
display: flex;
flex-direction: column;
justify-content: flex-end;
padding: 0;
}
.quick-item { .quick-item {
width: 80%; width: 80%;
margin: auto; margin: 0 auto 12px auto;
text-align: center; text-align: center;
line-height: 36px; line-height: 36px;
margin-top: 12px;
border: 1px solid $right-btn-color; border: 1px solid $right-btn-color;
color: $right-btn-color; color: $right-btn-color;
cursor: pointer; cursor: pointer;
border-radius: 4px; border-radius: 4px;
&:last-child { &:first-child {
margin-bottom: 12px; margin-top: 12px;
} }
} }