1
0
forked from dyf/dyf-vue-ui
Files
dyf-vue-ui/src/views/controlCenter/controlPanel/components/map.vue
2025-09-02 17:48:02 +08:00

322 lines
8.6 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="amap_page">
<div ref="mapRef" class="amap-container"></div>
<div class="content_top">
<!-- 左侧列表内容保持不变 -->
<div class="content_layout">
<div class="device_list">
<div class="list_header" v-if="!isSingleDevice">
<el-checkbox v-model="checkAll" @change="handleCheckAllChange" label="全选"></el-checkbox>
</div>
<div class="device_item" v-for="(device, index) in effectiveDeviceList" :key="device.id">
<el-checkbox :value="device.id" :model-value="checkedDeviceIds.includes(device.id)"
@update:model-value="(checked: boolean) => handleCheckboxChange(checked, device.id)"
class="device_checkbox" :hidden="isSingleDevice"></el-checkbox>
<div class="device_page">
<div class="device_info">
<div class="device_name">{{ device.deviceName }}</div>
<div class="device_model">{{ device.typeName }}</div>
<div class="device_status"
:class="{ online: device.onlineStatus === 1, offline: device.onlineStatus === 0 }">
{{ device.onlineStatus === 1 ? '在线' : '离线' }}
</div>
</div>
<el-button class="control_btn" @click="handleControl(device)">控制</el-button>
</div>
</div>
<div v-if="effectiveDeviceList.length === 0" class="nodata">
<img src="@/assets/images/nodata.png" alt="" class="nodataImg">
<div class="title">暂无数据</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { deviceVO } from '@/api/controlCenter/controlPanel/types';
import { defineProps, ref, watch, onMounted, onUnmounted, nextTick } from 'vue';
import { useRouter, useRoute } from 'vue-router';
const props = defineProps({
deviceList: {
type: Array as PropType<deviceVO[]>,
required: false,
default: () => []
}
});
const router = useRouter();
const route = useRoute();
declare var AMap: any;
// 核心状态
const mapRef = ref<HTMLDivElement | null>(null);
let mapInstance: any = null;
let infoWindow: any = null; // 信息窗口实例
const singleDeviceId = ref<string | null>(null);
const effectiveDeviceList = ref<deviceVO[]>([]);
const checkedDeviceIds = ref<string[]>([]); // 明确为字符串数组
const checkAll = ref(false);
const isSingleDevice = ref(false);
const activeDeviceName = ref(); // 当前激活的设备名称
// 初始化设备ID
const initSingleDeviceId = () => {
const idFromParams = route.params.deviceId as string;
const idFromQuery = route.query.deviceId as string;
singleDeviceId.value = idFromParams || idFromQuery || null;
isSingleDevice.value = !!singleDeviceId.value;
console.log('单设备模式:', isSingleDevice.value, '设备ID:', singleDeviceId.value);
};
const initDeviceList = () => {
if (!props.deviceList || !props.deviceList.length) {
effectiveDeviceList.value = [];
console.log('设备列表为空');
return;
}
if (isSingleDevice.value && singleDeviceId.value) {
const targetDevice = props.deviceList.find(
device => String(device.id) === String(singleDeviceId.value)
);
if (targetDevice) {
effectiveDeviceList.value = [targetDevice]; // 只显示匹配的单个设备
checkedDeviceIds.value = [targetDevice.id];
activeDeviceName.value = targetDevice.deviceName;
} else {
effectiveDeviceList.value = []; // 未找到匹配设备
}
} else {
// 多设备模式:显示所有设备
effectiveDeviceList.value = props.deviceList;
}
};
// 初始化地图(重点修复部分)
const initMap = async () => {
await nextTick();
if (!window.AMap || !mapRef.value) {
return;
}
// 销毁旧实例
if (mapInstance) mapInstance.destroy();
// 创建地图实例确保容器DOM已渲染
mapInstance = new AMap.Map(mapRef.value, {
center: getCenterCoord(),
zoom: isSingleDevice.value ? 5 : 5,
resizeEnable: true
});
// 初始化信息窗口(用于显示设备名称)
infoWindow = new AMap.InfoWindow({
offset: new AMap.Pixel(0, -30) // 偏移量,避免遮挡标记
});
// 渲染标记
renderMarkers();
};
// 获取中心坐标(确保数字类型)
const getCenterCoord = () => {
if (!effectiveDeviceList.value.length) {
return [114.4074, 30.5928];
}
// 遍历找第一个有效坐标
const validDevice = effectiveDeviceList.value.find(device => {
const lng = Number(device.longitude);
const lat = Number(device.latitude);
return !isNaN(lng) && !isNaN(lat) && lng !== 0 && lat !== 0;
});
if (validDevice) {
const lng = Number(validDevice.longitude);
const lat = Number(validDevice.latitude);
return [lng, lat];
}
// 无有效坐标时用默认
return [114.4074, 30.5928];
};
// 渲染标记(核心修复点)
const renderMarkers = () => {
if (!mapInstance || !effectiveDeviceList.value.length) return;
mapInstance.clearMap();
effectiveDeviceList.value.forEach(device => {
const lng = device.longitude !== null && device.longitude !== undefined ? Number(device.longitude) : NaN;
const lat = device.latitude !== null && device.latitude !== undefined ? Number(device.latitude) : NaN;
if (isNaN(lng) || isNaN(lat) || lng === 0 || lat === 0) {
return;
}
// 3. 创建标记(确保绑定到地图实例)
const marker = new AMap.Marker({
position: [lng, lat],
title: device.deviceName,
map: mapInstance,
icon: new AMap.Icon({
size: new AMap.Size(32, 32), // Marker显示尺寸
image: '/src/assets/images/position_ico.png',
imageSize: new AMap.Size(32, 32)
})
});
// 4. 标记点击事件
marker.on('click', () => {
activeDeviceName.value = device.deviceName;
infoWindow.setContent(`<div style="padding:8px">${device.deviceName}</div>`);
infoWindow.open(mapInstance, [lng, lat]);
});
});
};
// 全选处理
const handleCheckAllChange = (val: boolean) => {
if (isSingleDevice.value) return;
checkedDeviceIds.value = val ? effectiveDeviceList.value.map(d => d.id) : [];
};
// 处理单个复选框变更
const handleCheckboxChange = (checked: boolean, deviceId: string) => {
if (isSingleDevice.value) return;
if (checked) {
if (!checkedDeviceIds.value.includes(deviceId)) {
checkedDeviceIds.value.push(deviceId);
}
} else {
const index = checkedDeviceIds.value.indexOf(deviceId);
if (index > -1) {
checkedDeviceIds.value.splice(index, 1);
}
}
};
// 控制按钮点击
const handleControl = (device: deviceVO) => {
router.push(`/controlCenter/6170/${device.id}`);
};
// 监听路由和设备列表变化
watch(() => [route.params, route.query], () => {
initSingleDeviceId();
initDeviceList();
initMap();
}, { deep: true });
watch(() => props.deviceList, () => {
initDeviceList();
renderMarkers();
}, { deep: true });
// 初始化
onMounted(() => {
initSingleDeviceId();
initDeviceList();
initMap();
});
onUnmounted(() => {
if (mapInstance) mapInstance.destroy();
});
</script>
<style scoped>
/* 原有样式保持不变 */
.amap_page {
position: relative;
}
.amap-container {
height: 640px;
border-radius: 4px;
overflow: hidden;
width: 100%;
}
/* 设备名称提示框 */
.content_top {
width: 210px;
border-radius: 4px;
box-shadow: 0px 0px 6px 0px rgba(0, 34, 96, 0.1);
background: rgba(255, 255, 255, 1);
height: 620px;
position: absolute;
z-index: 1;
top: 10px;
left: 10px
}
/* 其他样式保持不变... */
.list_header {
padding: 12px;
border-bottom: 1px solid #eee;
font-weight: 600;
}
.device_item {
padding: 0px 12px;
display: flex;
align-items: center;
cursor: default;
transition: background 0.2s;
}
.device_checkbox {
flex-shrink: 0;
}
.device_page {
background-color: rgba(247, 248, 252, 1);
margin-bottom: 5px;
width: 84%;
padding: 5px;
border-radius: 4px;
position: relative;
}
.device_name {
font-weight: 500;
font-size: 14px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.device_model {
font-size: 12px;
color: #666;
margin: 4px 0;
}
.device_status {
font-size: 12px;
}
.online {
color: #00b42a;
}
.offline {
color: #f53f3f;
}
.control_btn {
font-size: 12px;
padding: 0px 15px;
flex-shrink: 0;
position: absolute;
right: 16px;
bottom: 10px;
background: rgba(2, 124, 251, 0.06);
color: rgba(2, 124, 251, 1);
border: none;
}
.nodata{
transform: translate(-1%,100%);
left:50%;
height: 30vh;
}
.nodataImg{
width: 130px;
}
.title{
color: #666;
padding-top: 20px;
}
</style>