2025年05月20日14:28:51

This commit is contained in:
luojiayi 2025-05-20 14:29:16 +08:00
parent b0f807597d
commit 22382d746e
11 changed files with 277 additions and 89 deletions

1
components.d.ts vendored
View File

@ -45,6 +45,7 @@ declare module '@vue/runtime-core' {
ElUpload: typeof import('element-plus/es')['ElUpload']
Header: typeof import('./src/components/header.vue')['default']
InfoWindow: typeof import('./src/components/InfoWindow.vue')['default']
LocationList: typeof import('./src/components/locationList.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
SectionDate: typeof import('./src/components/sectionDate.vue')['default']

View File

@ -0,0 +1,126 @@
<template>
<div class="locationList" :class="[show ? 'listShow' : 'listHidden']">
<div class="head">
<div>定位记录</div>
<div class="headicon">
<el-icon @click="show = false"><Fold /></el-icon>
</div>
</div>
<div class="list scrollbar">
<div class="item" v-for="item in list" :key="item.id" @click="emit('click', item)" v-if="list.length">
<div class="item-top">
<div class="item-name">IMEI:{{ item.deviceId }}</div>
<div class="item-time">定位时间{{ item.locationTime }}</div>
</div>
<div class="item-address">{{ item.address }}</div>
</div>
<div v-else>
<el-empty description="暂无数据" />
</div>
</div>
</div>
<div :class="[show ? 'iconHidden' : 'iconShow']" @click="show = true">
<div class="icon">
<el-icon><Expand /></el-icon>
</div>
</div>
</template>
<script setup lang="ts">
import { PropType, ref } from "vue";
import { TLocateRecord } from "@/api/index.d";
const show = ref(false);
defineProps({
list: {
type: Object as PropType<TLocateRecord.TRes[]>,
default: () => [],
},
});
const emit = defineEmits(["click"]);
</script>
<style scoped lang="less">
.locationList {
position: relative;
box-shadow: 0px 0px 12px rgba(0, 0, 0, 0.12);
flex: 1;
margin: 10px;
padding: 10px;
z-index: 2;
width: 250px;
background: #fff;
border-radius: 8px;
overflow: hidden;
display: flex;
flex-direction: column;
cursor: pointer;
transition: 0.2s;
.head {
display: flex;
align-items: center;
justify-content: space-between;
padding-bottom: 10px;
border-bottom: 1px solid #dedede;
.headicon {
width: 30px;
display: flex;
align-items: center;
justify-content: center;
}
}
.list {
flex: 1;
}
.item {
border-bottom: 1px solid #dedede;
padding: 10px;
cursor: pointer;
.item-top {
.item-name {
font-size: 16px;
color: #061451;
}
.item-time {
font-size: 14px;
color: #787878;
margin: 5px 0;
}
}
.item-address {
font-size: 14px;
color: #787878;
}
}
}
.icon {
position: absolute;
z-index: 3;
width: 30px;
height: 30px;
left: 10px;
top: 10px;
display: flex;
align-items: center;
justify-content: center;
background-color: #fff;
cursor: pointer;
box-shadow: 0px 0px 12px rgba(0, 0, 0, 0.12);
}
.listHidden {
width: 0;
padding: 0;
box-shadow: none;
}
.listShow {
width: 250px;
box-shadow: 0px 0px 12px rgba(0, 0, 0, 0.12);
}
.iconHidden {
display: none;
}
.iconShow {
display: block;
}
</style>

View File

@ -50,20 +50,20 @@ const onMessage = (res) => {
};
onMounted(() => {
const isReload = localStorage.getItem("isReload");
if (isReload !== "true") {
ElMessageBox.alert("由于浏览器安全策略,用户必须点击屏幕才能播放告警声音", "提示", {
confirmButtonText: "OK",
callback: () => {
localStorage.setItem("isReload", "true");
},
});
}
// const isReload = localStorage.getItem("isReload");
// if (isReload !== "true") {
// ElMessageBox.alert("", "", {
// confirmButtonText: "OK",
// callback: () => {
// localStorage.setItem("isReload", "true");
// },
// });
// }
if (ws.socket == null) {
ws.connect();
}
ws.onMessage(onMessage);
// if (ws.socket == null) {
// ws.connect();
// }
// ws.onMessage(onMessage);
window.addEventListener("beforeunload", handleBeforeUnload);
});

View File

@ -149,11 +149,12 @@ export class MapCustom {
anchor: 'bottom-center',
isCustom: true,
content: document.querySelector('.infoBox'),
offset: new AMap.Pixel(-2, -40),
offset: new AMap.Pixel(-2, -30),
...option
})
}
//绘制轨迹线条
polyline(list) {
this.clearMap()

View File

@ -80,9 +80,10 @@ const handleSearch = () => {
let columns = ref([
{ type: "index", label: "序号", width: 55, align: "center" },
{ prop: "deviceId", label: "手铐IMEI号" },
{ prop: "adminName", label: "绑定用户" },
{ prop: "username", label: "警员号" },
{ prop: "userNumber", label: "佩戴者" },
{ prop: "adminName", label: "绑定用户" },
{ prop: "adminPhone", label: "用户号码" },
{ prop: "username", label: "警员号" },
{ prop: "warnType", label: "事件类型" },
{ prop: "createTime", label: "时间" },
{ prop: "status", label: "处理状态" },

View File

@ -4,6 +4,7 @@
<img :src="fullScreen" alt="" v-if="!isFullScreen" />
<img :src="narrow" alt="" v-else />
</div>
<LocationList :list="locationRecord" @click="handleClick" />
</div>
<div :style="{ display: 'none' }">
<InfoWindow class="infoBox" :value="locationInfo" @close="InfoWin.close()" />
@ -21,10 +22,13 @@ import startMarker from "@/assets/img/start-marker.png";
import fullScreen from "@/assets/img/fullScreen.png";
import narrow from "@/assets/img/narrow.png";
import { useFullScreen } from "@/utils/hooks";
import LocationList from "@/components/locationList.vue";
import { TLocateRecord } from "@/api/index.d";
let InfoWin = null;
let newMap = null;
let locationInfo = ref({});
let locationRecord = ref<TLocateRecord.TRes[]>([]);
const { isFullScreen, toggleFullScreen } = useFullScreen();
const props = defineProps({
deviceId: {
@ -50,6 +54,7 @@ const getLocateRecord = () => {
endDate: `${format(d, "YYYY-MM-DD")} 23:59:59`,
}).then((res) => {
newMap.clearMap();
locationRecord.value = res;
if (res && res.length) {
let list = res;
newMap.setCenter([list[0].lng, list[0].lat]);
@ -68,46 +73,65 @@ const getLocateRecord = () => {
});
let markers = [];
list.forEach((item, index) => {
if (list.length < 50) {
let marker: any = "";
if (index == 0) {
marker = newMap.marker({ icon: startIcon, position: [item.lng, item.lat], zIndex: 13 });
} else if (index == list.length - 1) {
marker = newMap.marker({ icon: endIcon, position: [item.lng, item.lat], zIndex: 13 });
} else {
marker = newMap.marker({ icon: ViaIcon, position: [item.lng, item.lat], zIndex: 12 });
} else if (index == list.length - 1) {
marker = newMap.marker({ icon: startIcon, position: [item.lng, item.lat], zIndex: 13 });
}
if (marker) {
marker.on("click", () => {
locationInfo.value = item;
InfoWin = newMap.infoWindow();
InfoWin.open(newMap.map, marker.getPosition());
});
markers.push(marker);
} else {
if (index % 5 == 0) {
let marker: any = "";
if (index == 0) {
marker = newMap.marker({ icon: endIcon, position: [item.lng, item.lat], zIndex: 13 });
} else if (index == list.length - 1) {
marker = newMap.marker({ icon: startIcon, position: [item.lng, item.lat], zIndex: 13 });
} else {
marker = newMap.marker({ icon: ViaIcon, position: [item.lng, item.lat], zIndex: 12 });
}
marker.on("click", () => {
locationInfo.value = item;
InfoWin = newMap.infoWindow();
InfoWin.open(newMap.map, marker.getPosition());
});
markers.push(marker);
}
}
// if (list.length < 50) {
// let marker: any = "";
// if (index == 0) {
// marker = newMap.marker({ icon: endIcon, position: [item.lng, item.lat], zIndex: 13 });
// } else if (index == list.length - 1) {
// marker = newMap.marker({ icon: startIcon, position: [item.lng, item.lat], zIndex: 13 });
// } else {
// marker = newMap.marker({ icon: ViaIcon, position: [item.lng, item.lat], zIndex: 12 });
// }
// marker.on("click", () => {
// locationInfo.value = item;
// InfoWin = newMap.infoWindow();
// InfoWin.open(newMap.map, marker.getPosition());
// });
// markers.push(marker);
// } else {
// if (index % 5 == 0) {
// let marker: any = "";
// if (index == 0) {
// marker = newMap.marker({ icon: startIcon, position: [item.lng, item.lat], zIndex: 13 });
// } else if (index == list.length - 1) {
// marker = newMap.marker({ icon: endIcon, position: [item.lng, item.lat], zIndex: 13 });
// } else {
// marker = newMap.marker({ icon: ViaIcon, position: [item.lng, item.lat], zIndex: 12 });
// }
// marker.on("click", () => {
// locationInfo.value = item;
// InfoWin = newMap.infoWindow();
// InfoWin.open(newMap.map, marker.getPosition());
// });
// markers.push(marker);
// }
// }
});
newMap.map.add(markers);
}
});
};
const handleClick = (val) => {
locationInfo.value = val;
InfoWin = newMap.infoWindow({ offset: "" });
InfoWin.open(newMap.map, newMap.lngLat(val.lng, val.lat));
};
onMounted(() => {
newMap = new MapCustom({ dom: "mapcontainer" });
getLocateRecord();
@ -119,19 +143,28 @@ onMounted(() => {
height: 400px;
flex-shrink: 0;
position: relative;
display: flex;
flex-direction: column;
.toolbox {
width: 32px;
height: 32px;
padding: 5px;
width: 30px;
height: 30px;
overflow: hidden;
position: absolute;
right: 20px;
top: 20px;
right: 10px;
top: 10px;
// padding: 5px;
background: #fff;
border-radius: 4px;
z-index: 2;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
img {
width: 15px;
height: 15px;
}
}
}
</style>

View File

@ -179,9 +179,9 @@ const getLocateRecord = () => {
if (list.length < 50) {
let marker: any = "";
if (index == 0) {
marker = newMap.marker({ icon: startIcon, position: [item.lng, item.lat], zIndex: 13 });
} else if (index == list.length - 1) {
marker = newMap.marker({ icon: endIcon, position: [item.lng, item.lat], zIndex: 13 });
} else if (index == list.length - 1) {
marker = newMap.marker({ icon: startIcon, position: [item.lng, item.lat], zIndex: 13 });
} else {
marker = newMap.marker({ icon: ViaIcon, position: [item.lng, item.lat], zIndex: 12 });
}

View File

@ -164,6 +164,7 @@ const viewHistory = (row: TDevice.IUseRecordRes) => {
};
onUnmounted(() => {
sessionStorage.removeItem("query");
console.log(111);
});
</script>

View File

@ -5,7 +5,9 @@
<span>时间区间</span>
<SectionDate @change="sectionDateChange" />
</div>
<div id="mapcontainer" style="flex: 1"></div>
<div id="mapcontainer" style="flex: 1">
<LocationList :list="locationRecord" @click="handleClick" />
</div>
</div>
<div :style="{ display: 'none' }">
<InfoWindow class="infoBox" :value="deviceInfo" @close="InfoWin.close()" />
@ -26,11 +28,13 @@ import ViaMarker from "@/assets/img/via-marker.png";
import endMarker from "@/assets/img/end-marker.png";
import startMarker from "@/assets/img/start-marker.png";
import SectionDate from "@/components/sectionDate.vue";
import LocationList from "@/components/locationList.vue";
const { query } = useRoute();
let d = new Date();
let newMap = null;
let deviceInfo = ref({});
let locationRecord = ref<TLocateRecord.TRes[]>([]);
let InfoWin = null;
const params = reactive<TLocateRecord.TReq>({
@ -48,7 +52,7 @@ const sectionDateChange = (val) => {
const getLocateRecord = () => {
locateRecord(params).then((res) => {
newMap.clearMap();
locationRecord.value = res;
if (res && res.length) {
let list = res;
newMap.setCenter([list[0].lng, list[0].lat]);
@ -61,30 +65,31 @@ const getLocateRecord = () => {
image: endMarker,
size: [20, 29],
});
let ViaIcon = newMap.newIcon({
image: ViaMarker,
size: [20, 29],
});
let markers = [];
list.forEach((item, index) => {
let marker: any = "";
if (index == 0) {
marker = newMap.marker({ icon: startIcon, position: [item.lng, item.lat], zIndex: 13 });
} else if (index == list.length - 1) {
marker = newMap.marker({ icon: endIcon, position: [item.lng, item.lat], zIndex: 13 });
} else {
marker = newMap.marker({ icon: ViaIcon, position: [item.lng, item.lat], zIndex: 12 });
} else if (index == list.length - 1) {
marker = newMap.marker({ icon: startIcon, position: [item.lng, item.lat], zIndex: 13 });
}
marker.setMap(newMap.map);
if (marker) {
marker.on("click", () => {
deviceInfo.value = item;
InfoWin = newMap.infoWindow();
InfoWin.open(newMap.map, marker.getPosition());
});
});
} else {
markers.push(marker);
}
});
newMap.map.add(markers);
}
});
};
const handleClick = (val) => {
deviceInfo.value = val;
InfoWin = newMap.infoWindow({ offset: "" });
InfoWin.open(newMap.map, newMap.lngLat(val.lng, val.lat));
};
onMounted(() => {
@ -99,6 +104,11 @@ onMounted(() => {
<style scoped lang="less">
.container {
overflow: hidden;
#mapcontainer {
display: flex;
flex-direction: column;
}
.box {
background-color: #ffffff;
border: 1px solid #e4e7ed;

View File

@ -109,9 +109,29 @@ const ruleForm = ref<TDeviceConfig.Tres>({
outsideInterval: 0,
contacts: [],
});
//
const validateMobile = (value) => {
const reg = /^1[3-9]\d{9}$/;
return reg.test(value);
};
//
const validateLandline = (value) => {
//
// 1. 3-4-7-8
// 2. 7-8
const reg = /^(0\d{2,3}-)?\d{7,8}(-\d{1,6})?$/;
return reg.test(value);
};
//
const validatePhone = (value) => {
return validateMobile(value) || validateLandline(value);
};
const validate = (rule: any, value: any, callback: any) => {
if (rule.field == "name" && !ruleForm.value.contacts[0].name) return callback(new Error("请输入姓名1"));
if (rule.field == "phone" && !ruleForm.value.contacts[0].phone) return callback(new Error("请输入紧急电话1"));
if (rule.field == "phone" && !validatePhone(ruleForm.value.contacts[0].phone)) return ElMessage.error(`请输入有效的手机号或座机号1`);
callback();
};
const rules = reactive<FormRules<TDeviceConfig.Tres>>({
@ -128,9 +148,7 @@ const ruleFormRef = ref<FormInstance>();
const getDeviceConfig = () => {
deviceConfig({ deviceId: query.deviceId }).then((res) => {
let arr = res.contacts
.sort((a: any, b: any) => a.id - b.id)
.map((item) => {
let arr = res.contacts.map((item) => {
return {
name: item.name,
phone: item.phone,
@ -160,6 +178,9 @@ const submitForm = async (formEl: FormInstance | undefined) => {
if (item.name && !item.phone) {
return ElMessage.error(`请输入紧急电话${i + 1}`);
}
if (item.name && item.phone && !validatePhone(item.phone)) {
return ElMessage.error(`请输入有效的手机号或座机号${i + 1}`);
}
}
deviceConfigModify({
...ruleForm.value,

View File

@ -211,26 +211,20 @@ const handleDel = (row: TableItem) => {
accountDeletet({ id: row.id }).then((res) => {
ElMessage.success("删除成功");
getData();
comm.getRoleList();
});
});
};
const updateData = (res) => {
const updateData = (params) => {
let api, msg, p;
if (dialogTitle.value == "重置密码") {
api = passwordReset;
p = { ...res, id: rowData.value.id };
msg = "重置成功";
} else {
p = { ...res };
api = isEdit.value ? accountModify : accountAdd;
msg = isEdit.value ? "修改成功" : "新增成功";
}
api(p).then((res) => {
api = params.id ? accountModify : accountAdd;
msg = params.id ? "修改成功" : "新增成功";
api(params).then(() => {
ElMessage.success(msg);
closeDialog();
getData();
comm.getRoleList();
});
};
const handleResetPwd = (res) => {