forked from dyf/dyf-vue-ui
设备定位地图,打点展示
This commit is contained in:
BIN
src/assets/images/position_ico.png
Normal file
BIN
src/assets/images/position_ico.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.7 KiB |
@ -163,6 +163,7 @@ aside {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.components-container {
|
||||
margin: 30px 50px;
|
||||
position: relative;
|
||||
@ -214,4 +215,4 @@ aside {
|
||||
vertical-align: middle;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
}
|
@ -397,6 +397,7 @@ const saveBtn = () => {
|
||||
// 强制报警
|
||||
const forceAlarm = async () => {
|
||||
try {
|
||||
await proxy?.$modal.confirm('确定要对该设备开启强制报警?','');
|
||||
forceAlarmLoading.value = true
|
||||
// 2. 准备请求数据
|
||||
const batchId = generateShortId();
|
||||
@ -513,6 +514,9 @@ onMounted(() => {
|
||||
.online {
|
||||
color: #00ff00;
|
||||
}
|
||||
.offline{
|
||||
color: rgb(224, 52, 52);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,20 +2,17 @@
|
||||
<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">
|
||||
<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_item" v-for="(device, index) in props.deviceList" :key="index">
|
||||
<!-- 复选框 -->
|
||||
<el-checkbox :value="device.id" v-model="checkedDeviceIds" class="device_checkbox"></el-checkbox>
|
||||
|
||||
<!-- 设备信息 -->
|
||||
<div class="device_page">
|
||||
<div class="device_info">
|
||||
<div class="device_name">{{ device.deviceName }}</div>
|
||||
@ -25,124 +22,192 @@
|
||||
{{ 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 { 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[]>, // 用PropType指定数组元素为DeviceItem
|
||||
type: Array as PropType<deviceVO[]>,
|
||||
required: false,
|
||||
default: () => [] // 数组/对象类型的默认值必须用函数返回,避免引用共享
|
||||
default: () => []
|
||||
}
|
||||
});
|
||||
console.log(props.deviceList);
|
||||
const router = useRouter();
|
||||
// 声明高德地图全局类型
|
||||
const route = useRoute();
|
||||
declare var AMap: any;
|
||||
// 地图实例
|
||||
// 核心状态
|
||||
const mapRef = ref<HTMLDivElement | null>(null);
|
||||
let mapInstance: any = null;
|
||||
// 复选框状态管理
|
||||
const checkedDeviceIds = ref(); // 存储选中的设备ID
|
||||
const checkAll = ref(false); // 全选状态
|
||||
// 全选/取消全选
|
||||
const handleCheckAllChange = (val: boolean) => {
|
||||
checkedDeviceIds.value = val
|
||||
? props.deviceList.map(device => device.id) // 全选:选中所有设备ID
|
||||
: []; // 取消全选:清空
|
||||
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;
|
||||
}
|
||||
|
||||
// 监听单个复选框变化,更新全选状态
|
||||
watch(checkedDeviceIds, (newVal) => {
|
||||
checkAll.value = newVal.length === props.deviceList.length && props.deviceList.length > 0;
|
||||
});
|
||||
|
||||
// 设备控制事件
|
||||
const handleControl = (device: any) => {
|
||||
console.log('控制设备:', device);
|
||||
const deviceId = device.id;
|
||||
router.push('/controlCenter/6170/' + deviceId);
|
||||
};
|
||||
// 新增:获取地图中心坐标(优先用设备数据,无则用默认)
|
||||
const getCenterCoord = () => {
|
||||
// 1. 遍历设备列表,找第一个有有效经纬度的设备
|
||||
const validDevice = props.deviceList.find(
|
||||
(device:any) =>
|
||||
device.longitude !== undefined &&
|
||||
device.longitude !== null &&
|
||||
device.latitude !== undefined &&
|
||||
device.latitude !== null &&
|
||||
!isNaN(Number(device.longitude)) && // 确保是数字(避免字符串空值/非数字)
|
||||
!isNaN(Number(device.latitude))
|
||||
);
|
||||
|
||||
// 2. 有有效设备则用它的坐标,否则用默认值
|
||||
if (validDevice) {
|
||||
return [Number(validDevice.longitude), Number(validDevice.latitude)]; // 转数字防字符串坐标
|
||||
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 {
|
||||
return [114.4074, 30.5928]; // 默认中心坐标
|
||||
// 多设备模式:显示所有设备
|
||||
effectiveDeviceList.value = props.deviceList;
|
||||
}
|
||||
};
|
||||
// 地图初始化(修改center配置)
|
||||
const initMap = () => {
|
||||
if (!window.AMap || !mapRef.value) return;
|
||||
// 2. 调用函数获取中心坐标(不再用固定值)
|
||||
const centerCoord = getCenterCoord();
|
||||
// 初始化地图(重点修复部分)
|
||||
const initMap = async () => {
|
||||
await nextTick();
|
||||
if (!window.AMap || !mapRef.value) {
|
||||
return;
|
||||
}
|
||||
// 销毁旧实例
|
||||
if (mapInstance) mapInstance.destroy();
|
||||
// 创建地图实例(确保容器DOM已渲染)
|
||||
mapInstance = new AMap.Map(mapRef.value, {
|
||||
center: centerCoord, // 改用动态获取的坐标
|
||||
zoom: 15,
|
||||
center: getCenterCoord(),
|
||||
zoom: isSingleDevice.value ? 5 : 5,
|
||||
resizeEnable: true
|
||||
});
|
||||
// 后续的设备打点逻辑不变...
|
||||
if (Array.isArray(props.deviceList)) {
|
||||
props.deviceList.forEach(device => {
|
||||
console.log(device, 'devicedevice');
|
||||
if (device.longitude && device.latitude) {
|
||||
new AMap.Marker({
|
||||
position: [device.longitude, device.latitude],
|
||||
title: device.deviceName,
|
||||
map: mapInstance
|
||||
});
|
||||
}
|
||||
|
||||
// 初始化信息窗口(用于显示设备名称)
|
||||
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: 'https://a.amap.com/jsapi_demos/static/demo-center/icons/poi-marker-default.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);
|
||||
}
|
||||
}
|
||||
};
|
||||
watch(
|
||||
() => props.deviceList, // 监听props中的deviceList
|
||||
(newDeviceList) => {
|
||||
if (!mapInstance || !Array.isArray(newDeviceList)) return;
|
||||
// 1. 清除地图上已有的所有标记(避免重复打点)
|
||||
mapInstance.clearMap();
|
||||
// 2. 重新添加新的设备标记
|
||||
newDeviceList.forEach((device:any) => {
|
||||
console.log(device, 'device');
|
||||
// 确保设备有经纬度(lng和lat),避免无效打点
|
||||
if (device.longitude && device.latitude) {
|
||||
new AMap.Marker({
|
||||
position: [device.longitude, device.latitude],
|
||||
title: device.deviceName, // 用设备名当标题(匹配父组件字段)
|
||||
map: mapInstance,
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
{ deep: true } // 深度监听(如果设备列表里的子对象变化也能触发)
|
||||
);
|
||||
|
||||
// 控制按钮点击
|
||||
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(() => {
|
||||
initMap();
|
||||
initSingleDeviceId();
|
||||
initDeviceList();
|
||||
initMap();
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
@ -151,7 +216,7 @@ onUnmounted(() => {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 地图容器 */
|
||||
/* 原有样式保持不变 */
|
||||
.amap_page {
|
||||
position: relative;
|
||||
}
|
||||
@ -163,6 +228,7 @@ onUnmounted(() => {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 设备名称提示框 */
|
||||
.content_top {
|
||||
width: 210px;
|
||||
border-radius: 4px;
|
||||
@ -175,31 +241,25 @@ onUnmounted(() => {
|
||||
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;
|
||||
@ -235,7 +295,6 @@ onUnmounted(() => {
|
||||
color: #f53f3f;
|
||||
}
|
||||
|
||||
/* 控制按钮 */
|
||||
.control_btn {
|
||||
font-size: 12px;
|
||||
padding: 0px 15px;
|
||||
@ -247,4 +306,16 @@ onUnmounted(() => {
|
||||
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>
|
||||
|
@ -81,7 +81,7 @@
|
||||
<el-card class="Maplist">
|
||||
<div v-if="isListView" key="list">
|
||||
<el-table v-loading="loading" border :data="deviceList" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="50" align="center" />
|
||||
<el-table-column type="selection" width="50" align="center" :selectable="isSelectable" />
|
||||
<el-table-column label="设备名称" align="center" prop="deviceName" />
|
||||
<el-table-column label="设备图片" align="center" prop="devicePic">
|
||||
<template #default="scope">
|
||||
@ -104,13 +104,24 @@
|
||||
</el-table-column>
|
||||
<el-table-column label="设备IMEI" align="center" prop="deviceImei" />
|
||||
<el-table-column label="使用人员" align="center" prop="personnelBy" />
|
||||
<el-table-column label="电量" align="center" prop="battery" />
|
||||
<el-table-column label="设备状态" align="center" prop="onlineStatus">
|
||||
<template #default="scope">
|
||||
<div class="normal green" v-if="scope.row.onlineStatus == 1">在线</div>
|
||||
<div class="normal red" v-if="scope.row.onlineStatus == 0">离线</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="电量" align="center" prop="battery">
|
||||
<template #default="scope">
|
||||
<div :class="{
|
||||
'battery-red': Number(scope.row.battery) < 20,
|
||||
'battery-yellow': Number(scope.row.battery) >= 20 && Number(scope.row.battery) < 80,
|
||||
'battery-green': Number(scope.row.battery) >= 80 && Number(scope.row.battery) <= 100
|
||||
}">
|
||||
{{ scope.row.battery }}%
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="操作" fixed="right" width="180" class-name="small-padding fixed-width">
|
||||
<template #default="scope">
|
||||
<el-button link type="primary" @click="handleControl(scope.row)">控制面板</el-button>
|
||||
@ -127,11 +138,11 @@
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-dialog v-model="dialog.visible" :title="dialog.title" width="500px" append-to-body>
|
||||
<div class="title_ps">{{ `对${arrayDeviceName.join('、')}设备发送消息` }}</div>
|
||||
<el-form ref="postFormRef" :model="form" label-width="80px">
|
||||
<el-form-item label="发送信息" prop="messageToSend">
|
||||
<el-input type="textarea" v-model="form.messageToSend" placeholder="请输入内容" />
|
||||
</el-form-item>
|
||||
<el-input type="textarea" v-model="form.messageToSend" placeholder="请输入消息内容" />
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button type="primary" @click="submitForm" :loading="sendTextLoading"
|
||||
@ -207,8 +218,12 @@ const switchView = (view: 'list' | 'map') => {
|
||||
};
|
||||
const dialog = reactive<DialogOption>({
|
||||
visible: false,
|
||||
title: ''
|
||||
title: '发送信息'
|
||||
});
|
||||
const isSelectable = (row: any) => {
|
||||
// 仅当在线状态(onlineStatus == 1)时允许选中
|
||||
return row.onlineStatus === 1;
|
||||
}
|
||||
/** 通过条件过滤节点 */
|
||||
const filterNode = (value: string, data: any) => {
|
||||
if (!value) return true;
|
||||
@ -286,12 +301,17 @@ const resetQuery = () => {
|
||||
|
||||
/** 设备控制跳转 */
|
||||
const handleControl = (row: any) => {
|
||||
console.log(row,'row1111');
|
||||
|
||||
const deviceId = row.id;
|
||||
router.push('/controlCenter/6170/' + deviceId);
|
||||
};
|
||||
|
||||
/** 选择条数 */
|
||||
const arrayDeviceName = ref([])
|
||||
const handleSelectionChange = (selection: deviceVO[]) => {
|
||||
console.log(selection, 'selectionselection');
|
||||
arrayDeviceName.value = selection.map(item => item.deviceName);
|
||||
ids.value = selection;
|
||||
single.value = selection.length != 1;
|
||||
multiple.value = !selection.length;
|
||||
@ -397,7 +417,7 @@ const submitForm = async () => {
|
||||
};
|
||||
// 取消
|
||||
const cancel = () => {
|
||||
dialog.visible = true;
|
||||
dialog.visible = false;
|
||||
form.value.messageToSend = ''
|
||||
}
|
||||
// 强制报警
|
||||
@ -411,6 +431,7 @@ const forceAlarm = async () => {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await proxy?.$modal.confirm('确定要对所选设备开启强制报警?', '强制报警');
|
||||
forceAlarmLoading.value = true
|
||||
// 2. 准备请求数据
|
||||
const batchId = generateShortId();
|
||||
@ -465,6 +486,25 @@ const forceAlarm = async () => {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.battery-red {
|
||||
color: rgba(224, 52, 52, 1);
|
||||
}
|
||||
|
||||
/* 20%~80% 黄色(或橙色,根据设计图调整) */
|
||||
.battery-yellow {
|
||||
color: rgba(234, 152, 0, 1);
|
||||
}
|
||||
|
||||
/* 80%~100% 绿色 */
|
||||
.battery-green {
|
||||
color: rgba(234, 152, 0, 1);
|
||||
}
|
||||
|
||||
.title_ps {
|
||||
color: rgba(224, 52, 52, 1);
|
||||
padding: 0px 0 20px 0;
|
||||
}
|
||||
|
||||
.main-tree {
|
||||
border-radius: 4px;
|
||||
box-shadow: 0px 0px 6px 0px rgba(0, 34, 96, 0.1);
|
||||
|
@ -50,14 +50,26 @@
|
||||
<script setup lang="ts">
|
||||
import api from "@/api/controlCenter/historyjectory/index"
|
||||
import { formatTimestampToHM } from "@/utils/function"
|
||||
import { useRoute } from "vue-router"
|
||||
import { ref, onMounted, onUnmounted, watch } from "vue"
|
||||
import { ElMessage } from "element-plus"
|
||||
|
||||
// 添加AMap类型声明
|
||||
declare global {
|
||||
interface Window {
|
||||
AMap: any;
|
||||
}
|
||||
}
|
||||
|
||||
declare var AMap: any;
|
||||
const route = useRoute();
|
||||
// -------------------------- 状态管理 --------------------------
|
||||
// 地图实例
|
||||
const mapRef = ref<AMap.Map | null>(null);
|
||||
const mapRef = ref<any>(null);
|
||||
// 轨迹线实例
|
||||
const polylineRef = ref<AMap.Polyline | null>(null);
|
||||
const polylineRef = ref<any>(null);
|
||||
// 移动标记点实例
|
||||
const markerRef = ref<AMap.Marker | null>(null);
|
||||
const markerRef = ref<any>(null);
|
||||
// 轨迹数据(初始为空,从设备列表动态获取)
|
||||
const trackPoints = ref<[number, number][]>([]); // 移除硬编码默认值
|
||||
// 设备分组数据(包含各设备的轨迹点)
|
||||
@ -92,21 +104,19 @@ const initMap = () => {
|
||||
polylineRef.value = polyline;
|
||||
// 创建标记点(初始位置临时设为 [0, 0],播放时会覆盖)
|
||||
const marker = new AMap.Marker({
|
||||
position: [0, 0],
|
||||
position: [20, 20],
|
||||
icon: new AMap.Icon({
|
||||
size: new AMap.Size(40, 40),
|
||||
image: 'https://webapi.amap.com/images/car.png',
|
||||
image: '/src/assets/images/position_ico.png',
|
||||
imageSize: new AMap.Size(40, 40),
|
||||
}),
|
||||
anchor: 'center',
|
||||
});
|
||||
map.add(marker);
|
||||
markerRef.value = marker;
|
||||
|
||||
isLoading.value = false;
|
||||
fetchTrackData(); // 数据请求移到地图初始化后
|
||||
} catch (error) {
|
||||
console.error('地图初始化失败:', error);
|
||||
isLoading.value = false;
|
||||
}
|
||||
};
|
||||
@ -254,11 +264,11 @@ watch(trackPoints, (newPoints) => {
|
||||
|
||||
<style scoped>
|
||||
/* 样式保持不变 */
|
||||
:deep .el-timeline-item__tail {
|
||||
:deep(.el-timeline-item__tail) {
|
||||
border-left: 1px solid rgba(0, 198, 250, 0.2);
|
||||
}
|
||||
|
||||
:deep .el-timeline-item__timestamp {
|
||||
:deep(.el-timeline-item__timestamp) {
|
||||
color: rgba(2, 124, 251, 1);
|
||||
font-size: 16px;
|
||||
}
|
||||
@ -446,4 +456,4 @@ watch(trackPoints, (newPoints) => {
|
||||
color: #666;
|
||||
padding-top: 20px;
|
||||
}
|
||||
</style>
|
||||
</style>
|
@ -3,48 +3,60 @@
|
||||
<transition :enter-active-class="proxy?.animate.searchAnimate.enter"
|
||||
:leave-active-class="proxy?.animate.searchAnimate.leave">
|
||||
<div v-show="showSearch" class="mb-[10px]">
|
||||
<el-card shadow="hover">
|
||||
<el-button :type="isListView ? 'primary' : ''" @click="switchView('card')">
|
||||
<el-card shadow="hover" class="btn_wev">
|
||||
<el-button :type="isListView ? 'primary' : ''" @click="switchView('card')" class="btn_list">
|
||||
{{ isListView ? '卡片显示' : '卡片显示' }}
|
||||
</el-button>
|
||||
<el-button :type="!isListView ? 'primary' : ''" @click="switchView('list')">
|
||||
<el-button :type="!isListView ? 'primary' : ''" @click="switchView('list')" class="btn_list">
|
||||
{{ !isListView ? '列表显示' : '列表显示' }}
|
||||
</el-button>
|
||||
<el-form ref="queryFormRef" :model="queryParams" :inline="true" style="margin-top: 20px;">
|
||||
<el-form-item label="设备名称" prop="deviceName">
|
||||
<el-input v-model="queryParams.deviceName" placeholder="请输入设备名称" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="设备类型" prop="deviceType">
|
||||
<el-select v-model="queryParams.deviceType" placeholder="设备类型">
|
||||
<el-option v-for="item in deviceTypeOptions" :key="item.value" :label="item.typeName"
|
||||
:value="item.deviceTypeId" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="报警事项" prop="deviceAction">
|
||||
<el-select v-model="queryParams.deviceAction">
|
||||
<el-option value="0" label="强制报警"></el-option>
|
||||
<el-option value="1" label="撞击闯入"></el-option>
|
||||
<el-option value="2" label="手动报警"></el-option>
|
||||
<el-option value="3" label="电子围栏告警"></el-option>
|
||||
<el-option value="4" label="强制告警"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="报警时间" style="width: 308px">
|
||||
<el-date-picker v-model="dateRange" value-format="YYYY-MM-DD HH:mm:ss" type="daterange"
|
||||
range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期"
|
||||
:default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"></el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item label="报警状态" prop="treatmentState">
|
||||
<el-select v-model="queryParams.treatmentState">
|
||||
<el-option value="0" label="已处理"></el-option>
|
||||
<el-option value="1" label="未处理"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="btn_search">
|
||||
<div style="position: absolute; right:30px; top:30px">
|
||||
<el-input v-model="queryParams.content" placeholder="报警信息" clearable
|
||||
style="width: 200px; margin-right: 20px;" @keyup.enter="handleQuery" @input="handleInput" />
|
||||
<el-button type="primary" plain @click="toggleFilter">高级筛选</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-collapse accordion v-model="activeNames">
|
||||
<el-collapse-item name="1">
|
||||
<el-form ref="queryFormRef" :model="queryParams" :inline="true" style="margin-top: 20px;">
|
||||
<el-form-item label="设备名称" prop="deviceName">
|
||||
<el-input v-model="queryParams.deviceName" placeholder="请输入设备名称" clearable
|
||||
@keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="设备类型" prop="deviceType">
|
||||
<el-select v-model="queryParams.deviceType" placeholder="设备类型">
|
||||
<el-option v-for="item in deviceTypeOptions" :key="item.value" :label="item.typeName"
|
||||
:value="item.deviceTypeId" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="报警事项" prop="deviceAction">
|
||||
<el-select v-model="queryParams.deviceAction">
|
||||
<el-option value="0" label="强制报警"></el-option>
|
||||
<el-option value="1" label="撞击闯入"></el-option>
|
||||
<el-option value="2" label="手动报警"></el-option>
|
||||
<el-option value="3" label="电子围栏告警"></el-option>
|
||||
<el-option value="4" label="强制告警"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="报警时间" style="width: 308px">
|
||||
<el-date-picker v-model="dateRange" value-format="YYYY-MM-DD HH:mm:ss" type="daterange"
|
||||
range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期"
|
||||
:default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"></el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item label="报警状态" prop="treatmentState">
|
||||
<el-select v-model="queryParams.treatmentState">
|
||||
<el-option value="0" label="已处理"></el-option>
|
||||
<el-option value="1" label="未处理"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</el-card>
|
||||
</div>
|
||||
</transition>
|
||||
@ -158,7 +170,8 @@ const showSearch = ref(true);
|
||||
const total = ref(0);
|
||||
const deviceTypeOptions = ref([]); //设备类型
|
||||
const queryFormRef = ref<ElFormInstance>();
|
||||
const alarmFormRef = ref<ElFormInstance>();
|
||||
const activeNames = ref([]);
|
||||
const debounceTimer = ref(null) // 用于防抖的定时器
|
||||
const initFormData: AlarmForm = {
|
||||
id: undefined,
|
||||
deviceId: undefined,
|
||||
@ -225,14 +238,23 @@ const getDeviceType = () => {
|
||||
|
||||
})
|
||||
};
|
||||
const toggleFilter = () => {
|
||||
if (activeNames.value.length > 0) {
|
||||
activeNames.value = [];
|
||||
} else {
|
||||
activeNames.value = ['1'];
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/** 表单重置 */
|
||||
const reset = () => {
|
||||
form.value = { ...initFormData };
|
||||
alarmFormRef.value?.resetFields();
|
||||
}
|
||||
|
||||
const handleInput = () => {
|
||||
if (debounceTimer.value) {
|
||||
clearTimeout(debounceTimer.value)
|
||||
}
|
||||
// 300ms后执行查询(避免输入过程中频繁调用接口)
|
||||
debounceTimer.value = setTimeout(() => {
|
||||
handleQuery() // 调用查询接口的方法
|
||||
}, 300)
|
||||
};
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.value.pageNum = 1;
|
||||
@ -250,8 +272,25 @@ onMounted(() => {
|
||||
});
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
:deep .el-collapse-item__header {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:deep .el-collapse-item__wrap {
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
.btn_wev {
|
||||
padding: 5px 0;
|
||||
}
|
||||
|
||||
.btn_list {
|
||||
margin-top: -5px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.custom-alarm-card {
|
||||
height: 298px;
|
||||
height: 270px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-shadow: 0px 0px 6px 0px rgba(0, 27, 74, 0.1);
|
||||
@ -320,7 +359,8 @@ onMounted(() => {
|
||||
.label {
|
||||
font-weight: 500;
|
||||
margin-right: 4px;
|
||||
margin: 12px 0;
|
||||
margin: 4px 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.value {
|
||||
@ -334,6 +374,7 @@ onMounted(() => {
|
||||
margin: 10px 0 10px 0;
|
||||
position: relative;
|
||||
color: rgb(224, 52, 52);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.alearm::before {
|
||||
@ -354,6 +395,7 @@ onMounted(() => {
|
||||
margin: 10px 0 10px 0;
|
||||
position: relative;
|
||||
color: rgba(56, 64, 79, 1);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.alearmADD::before {
|
||||
|
Reference in New Issue
Block a user