diff --git a/jsconfig.json b/jsconfig.json index 13f89b5..d01da91 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -27,8 +27,6 @@ "lib": ["dom", "esnext"], "types": ["@types/node"], "sourceMap": true, - "allowSyntheticDefaultImports": true, - "experimentalDecorators": true, "emitDecoratorMetadata": true, "noEmit": true, "isolatedModules": true, diff --git a/src/api/bst/device.js b/src/api/bst/device.js index 374afd0..574d0f8 100644 --- a/src/api/bst/device.js +++ b/src/api/bst/device.js @@ -107,4 +107,11 @@ export function unbindDeviceArea(ids) { }) } - +// 查询全部设备列表 +export function listAllDevice(query) { + return request({ + url: '/bst/device/all', + method: 'get', + params: query + }) +} diff --git a/src/views/bst/area/view/view.vue b/src/views/bst/area/view/view.vue index 8a5e59c..6fc77eb 100644 --- a/src/views/bst/area/view/view.vue +++ b/src/views/bst/area/view/view.vue @@ -23,7 +23,7 @@ - + diff --git a/src/views/bst/areaSub/components/AreaMap.js b/src/views/bst/areaSub/components/AreaMap.js index ecf14fa..e50d10a 100644 --- a/src/views/bst/areaSub/components/AreaMap.js +++ b/src/views/bst/areaSub/components/AreaMap.js @@ -90,7 +90,7 @@ export const DEVICE_STATUS_ICONS = { }; // SN标签样式 -export function getSnLabel(AMap, sn, position) { +export function getSnLabel(AMap, sn, position, color = '#409EFF') { return new AMap.Text({ text: sn, position: position, @@ -98,10 +98,10 @@ export function getSnLabel(AMap, sn, position) { anchor: 'center', style: { background: '#fff', - border: '1px solid #409EFF', + border: `1px solid ${color}`, borderRadius: '4px', padding: '2px 8px', - color: '#409EFF', + color: color, fontSize: '12px', whiteSpace: 'nowrap', textAlign: 'center', @@ -110,6 +110,25 @@ export function getSnLabel(AMap, sn, position) { }); } +// 获取设备图标 +export function getDeviceIcon(AMap, status) { + return new AMap.Icon({ + image: DEVICE_STATUS_ICONS[status], + size: new AMap.Size(32, 36), + imageSize: new AMap.Size(32, 36), + anchor: 'bottom-center' + }); +} + +// 获取设备标记 +export function getDeviceMaker(AMap, icon, position) { + return new AMap.Marker({ + position: position, + offset: new AMap.Pixel(-16, -36), + icon: icon + }); +} + // 公共方法 export const mapMethods = { // 开始绘制(不管是那种边界) diff --git a/src/views/bst/areaSub/components/AreaMap.scss b/src/views/bst/areaSub/components/AreaMap.scss index 4c505d6..e038059 100644 --- a/src/views/bst/areaSub/components/AreaMap.scss +++ b/src/views/bst/areaSub/components/AreaMap.scss @@ -23,4 +23,10 @@ color: #66b1ff; } } +} + +.device-list { + position: absolute; + top: 10px; + left: 10px; } \ No newline at end of file diff --git a/src/views/bst/areaSub/components/AreaMap.vue b/src/views/bst/areaSub/components/AreaMap.vue index 5b1817f..6bafca3 100644 --- a/src/views/bst/areaSub/components/AreaMap.vue +++ b/src/views/bst/areaSub/components/AreaMap.vue @@ -6,6 +6,11 @@ + +
+ +
+
@@ -13,7 +18,8 @@ 电子围栏 全局查看 切换样式 - {{ showLabels ? '隐藏' : '显示' }}标签 + {{ showLabels ? '隐藏' : '显示' }}标签 + 切换车辆展示 {{ isPlaybackVisible ? '隐藏' : '显示' }}轨迹控制台
@@ -59,15 +65,16 @@ import AddressSearch from '@/views/bst/areaSub/components/AddressSearch.vue'; import OperationArea from './mixins/OperationArea'; import SubArea from './mixins/SubArea'; import LocationLog from './mixins/LocationLog'; +import DeviceList from '@/views/bst/areaSub/components/DeviceList.vue' export default { name: 'AreaMap', components: { PlaybackPanel, - AddressSearch + AddressSearch, + DeviceList }, - mixins: [OperationArea, SubArea, LocationLog], - dicts: ['device_status', 'device_lock_status', 'device_quality'], + mixins: [OperationArea, SubArea, LocationLog], // 添加 DeviceList 混入 props: { // 运营区域信息 area: { @@ -88,6 +95,10 @@ export default { enableEdit: { type: Boolean, default: true + }, + deviceList: { + type: Array, + default: () => [] } }, data() { @@ -153,6 +164,8 @@ export default { contextMenu: true }); + this.map.on('click', this.onCickMap); + // 初始化多边形编辑器 this.polygonEditor = new this.AMap.PolygonEditor(this.map); @@ -163,6 +176,12 @@ export default { console.error('地图初始化失败:', error); } }, + onCickMap() { + this.closeAreaSubInfoWindow(); + if (this.$refs.deviceList) { + this.$refs.deviceList.closeDeviceInfoWindow(); + } + }, handlePolygonAdd({target}) { this.editingPolygon = target; }, @@ -280,6 +299,11 @@ export default { this.map = null; } }, + toggleDeviceShow() { + if (this.$refs.deviceList) { + this.$refs.deviceList.toggleDeviceShow(); + } + } } }; diff --git a/src/views/bst/areaSub/components/DeviceList.vue b/src/views/bst/areaSub/components/DeviceList.vue new file mode 100644 index 0000000..1dd9d65 --- /dev/null +++ b/src/views/bst/areaSub/components/DeviceList.vue @@ -0,0 +1,480 @@ + + + + + \ No newline at end of file diff --git a/src/views/bst/areaSub/components/mixins/DeviceList.js b/src/views/bst/areaSub/components/mixins/DeviceList.js new file mode 100644 index 0000000..933bbe3 --- /dev/null +++ b/src/views/bst/areaSub/components/mixins/DeviceList.js @@ -0,0 +1,182 @@ +import { getDeviceIcon, getDeviceMaker, getSnLabel } from "@/views/bst/areaSub/components/AreaMap.js"; + +export const $deviceList = { + props: { + deviceList: { + type: Array, + default: () => [] + }, + }, + data() { + return { + infoWindow: null, + currentDevice: null, + deviceOverlays: new Map(), + deviceLabelOverlays: new Map(), + showDevice: true, + } + }, + watch: { + deviceList(nv, ov) { + this.renderDeviceMarkers(nv); + } + }, + methods: { + // 新增方法,用于在地图上渲染设备标记 + renderDeviceMarkers() { + this.clearOverlays(this.deviceOverlays); + this.clearOverlays(this.deviceLabelOverlays); + if (this.deviceList == null) { + return; + } + this.deviceList.forEach(device => { + const position = [device.longitude, device.latitude]; + const icon = getDeviceIcon(this.AMap, device.status); + const marker = getDeviceMaker(this.AMap, icon, position); + let color = '#409EFF'; + // 根据电量设置不同颜色 + if (device.remainingPower <= 20) { + color = '#F56C6C'; // 红色 - 严重不足 + } else if (device.remainingPower <= 40) { + color = '#E6A23C'; // 橙色 - 电量不足 + } else if (device.remainingPower <= 60) { + color = '#409EFF'; // 蓝色 - 一般 + } else if (device.remainingPower <= 80) { + color = '#67C23A'; // 绿色 - 充足 + } else { + color = '#2ecc71'; // 深绿色 - 非常充足 + } + const snLabel = getSnLabel(this.AMap, `${device.vehicleNum || ''} ${device.remainingPower}%`, position, color); + + // 添加点击事件 + marker.on('click', (e) => { + this.showDeviceInfo(device, position); + }); + + this.deviceOverlays.set(device.id, marker); + this.deviceLabelOverlays.set(device.id, snLabel); + this.map.add([marker, snLabel]); + }); + }, + + // 关闭信息弹窗 + closeDeviceInfoWindow() { + if (this.infoWindow) { + this.infoWindow.close(); + this.currentDevice = null; + } + }, + + // 显示设备信息弹窗 + showDeviceInfo(device, position) { + if (!this.infoWindow) { + this.infoWindow = new this.AMap.InfoWindow({ + isCustom: true, + offset: new this.AMap.Pixel(0, -30), + autoMove: true + }); + } + + this.currentDevice = device; + const content = this.createInfoWindowContent(device); + this.infoWindow.setContent(content); + this.infoWindow.open(this.map, position); + }, + + // 创建信息弹窗内容 + createInfoWindowContent(device) { + const content = document.createElement('div'); + content.className = 'device-info-window'; + content.style.padding = '10px'; + content.style.minWidth = '300px'; + content.style.backgroundColor = 'rgba(255, 255, 255, 0.9)'; + content.style.borderRadius = '4px'; + content.style.boxShadow = '0 2px 12px 0 rgba(0, 0, 0, 0.1)'; + + const infoItems = [ + { label: '车牌号', value: device.vehicleNum || '--' }, + { label: '状态', value: this.getDictLabel('device_status', device.status) }, + { label: 'SN', value: device.sn }, + { label: 'MAC', value: device.mac }, + { label: '电压', value: `${device.voltage || 0} V` }, + { label: '电量', value: `${device.remainingPower}%` }, + { label: '信号', value: device.signal || '-' }, + { label: '卫星', value: device.satellites || '-' }, + { label: '锁状态', value: this.getDictLabel('device_lock_status', device.lockStatus) }, + { label: '电门', value: this.getDictLabel('device_quality', device.quality) }, + { label: '速度', value: `${device.speed || 0} KM/h` }, + ]; + + // 创建网格容器 + const gridContainer = document.createElement('div'); + gridContainer.style.display = 'grid'; + gridContainer.style.gridTemplateColumns = 'repeat(2, 1fr)'; + gridContainer.style.gap = '8px'; + gridContainer.style.fontSize = '12px'; + + infoItems.forEach(item => { + const itemContainer = document.createElement('div'); + itemContainer.style.display = 'flex'; + itemContainer.style.alignItems = 'center'; + itemContainer.style.marginBottom = '4px'; + + const label = document.createElement('span'); + label.style.color = '#606266'; + label.style.marginRight = '4px'; + label.textContent = `${item.label}:`; + + const value = document.createElement('span'); + value.style.color = '#303133'; + value.style.fontWeight = '500'; + value.textContent = item.value; + + itemContainer.appendChild(label); + itemContainer.appendChild(value); + gridContainer.appendChild(itemContainer); + }); + + content.appendChild(gridContainer); + + // 添加查看详情按钮 + const buttonContainer = document.createElement('div'); + buttonContainer.style.marginTop = '10px'; + buttonContainer.style.textAlign = 'center'; + + const detailButton = document.createElement('button'); + detailButton.textContent = '查看详情'; + detailButton.style.padding = '6px 12px'; + detailButton.style.backgroundColor = '#409EFF'; + detailButton.style.color = '#fff'; + detailButton.style.border = 'none'; + detailButton.style.borderRadius = '4px'; + detailButton.style.cursor = 'pointer'; + detailButton.style.fontSize = '12px'; + detailButton.onclick = () => { + this.closeDeviceInfoWindow(); + this.$router.push(`/view/device/${device.id}`); + }; + + buttonContainer.appendChild(detailButton); + content.appendChild(buttonContainer); + + return content; + }, + + // 获取字典标签 + getDictLabel(dictType, value) { + const dict = this.dict.type[dictType]; + if (!dict) return value; + const item = dict.find(item => item.value === value); + return item ? item.label : value; + }, + toggleDeviceShow() { + this.showDevice = !this.showDevice; + this.deviceOverlays.forEach(item => { + item.setMap(this.showDevice ? this.map : null); + }); + this.deviceLabelOverlays.forEach(item => { + item.setMap(this.showDevice ? this.map : null); + }); + } + } +} \ No newline at end of file diff --git a/src/views/bst/areaSub/components/mixins/LocationLog.js b/src/views/bst/areaSub/components/mixins/LocationLog.js index 2212f6f..206e96b 100644 --- a/src/views/bst/areaSub/components/mixins/LocationLog.js +++ b/src/views/bst/areaSub/components/mixins/LocationLog.js @@ -1,5 +1,5 @@ import { isEmpty } from "@/utils/index"; -import { DEVICE_STATUS_ICONS, getSnLabel } from "@/views/bst/areaSub/components/AreaMap"; +import { DEVICE_STATUS_ICONS, getDeviceIcon, getDeviceMaker, getSnLabel } from "@/views/bst/areaSub/components/AreaMap"; export default { data() { @@ -126,19 +126,10 @@ export default { if (!this.currentDeviceMarker) { // 创建图标对象 - this.currentDeviceIcon = new this.AMap.Icon({ - image: DEVICE_STATUS_ICONS[this.currentLog.status], - size: new this.AMap.Size(32, 36), - imageSize: new this.AMap.Size(32, 36), - anchor: 'bottom-center' - }); + this.currentDeviceIcon = getDeviceIcon(this.AMap, this.currentLog.status); // 首次创建设备标记 - this.currentDeviceMarker = new this.AMap.Marker({ - position: position, - offset: new this.AMap.Pixel(-16, -36), - icon: this.currentDeviceIcon - }); + this.currentDeviceMarker = getDeviceMaker(this.AMap, this.currentDeviceIcon, position); // 保存当前设备ID this.currentDeviceMarker.deviceId = this.currentLog.deviceId; diff --git a/src/views/bst/areaSub/components/mixins/SubArea.js b/src/views/bst/areaSub/components/mixins/SubArea.js index 834eac4..0d70daa 100644 --- a/src/views/bst/areaSub/components/mixins/SubArea.js +++ b/src/views/bst/areaSub/components/mixins/SubArea.js @@ -50,17 +50,16 @@ export default { methods: { initSubArea() { - // 添加地图点击事件,关闭信息窗体和清除选中状态 - this.map.on('click', () => { - if (this.currentInfoWindow) { - this.currentInfoWindow.close(); - } - if (!this.isEditing) { - this.selectedAreaId = null; - } - }); this.renderSubAreas(); }, + closeAreaSubInfoWindow() { + if (this.currentInfoWindow) { + this.currentInfoWindow.close(); + } + if (!this.isEditing) { + this.selectedAreaId = null; + } + }, // 渲染子区域 renderSubAreas() { diff --git a/src/views/bst/areaSub/index.vue b/src/views/bst/areaSub/index.vue index 51daaad..655248a 100644 --- a/src/views/bst/areaSub/index.vue +++ b/src/views/bst/areaSub/index.vue @@ -38,17 +38,18 @@ - - + @click="$router.push(`/operate/areaSub/${area.id}`)" + v-show="!enableEdit" + v-has-permi="['bst:areaSub:list']" + >前往编辑 + + - +