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'] ElUpload: typeof import('element-plus/es')['ElUpload']
Header: typeof import('./src/components/header.vue')['default'] Header: typeof import('./src/components/header.vue')['default']
InfoWindow: typeof import('./src/components/InfoWindow.vue')['default'] InfoWindow: typeof import('./src/components/InfoWindow.vue')['default']
LocationList: typeof import('./src/components/locationList.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink'] RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView'] RouterView: typeof import('vue-router')['RouterView']
SectionDate: typeof import('./src/components/sectionDate.vue')['default'] 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(() => { onMounted(() => {
const isReload = localStorage.getItem("isReload"); // const isReload = localStorage.getItem("isReload");
if (isReload !== "true") { // if (isReload !== "true") {
ElMessageBox.alert("由于浏览器安全策略,用户必须点击屏幕才能播放告警声音", "提示", { // ElMessageBox.alert("", "", {
confirmButtonText: "OK", // confirmButtonText: "OK",
callback: () => { // callback: () => {
localStorage.setItem("isReload", "true"); // localStorage.setItem("isReload", "true");
}, // },
}); // });
} // }
if (ws.socket == null) { // if (ws.socket == null) {
ws.connect(); // ws.connect();
} // }
ws.onMessage(onMessage); // ws.onMessage(onMessage);
window.addEventListener("beforeunload", handleBeforeUnload); window.addEventListener("beforeunload", handleBeforeUnload);
}); });

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -109,9 +109,29 @@ const ruleForm = ref<TDeviceConfig.Tres>({
outsideInterval: 0, outsideInterval: 0,
contacts: [], 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) => { 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 == "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" && !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(); callback();
}; };
const rules = reactive<FormRules<TDeviceConfig.Tres>>({ const rules = reactive<FormRules<TDeviceConfig.Tres>>({
@ -128,14 +148,12 @@ const ruleFormRef = ref<FormInstance>();
const getDeviceConfig = () => { const getDeviceConfig = () => {
deviceConfig({ deviceId: query.deviceId }).then((res) => { deviceConfig({ deviceId: query.deviceId }).then((res) => {
let arr = res.contacts let arr = res.contacts.map((item) => {
.sort((a: any, b: any) => a.id - b.id) return {
.map((item) => { name: item.name,
return { phone: item.phone,
name: item.name, };
phone: item.phone, });
};
});
ruleForm.value = { ruleForm.value = {
...res, ...res,
contacts: arr.length contacts: arr.length
@ -160,6 +178,9 @@ const submitForm = async (formEl: FormInstance | undefined) => {
if (item.name && !item.phone) { if (item.name && !item.phone) {
return ElMessage.error(`请输入紧急电话${i + 1}`); return ElMessage.error(`请输入紧急电话${i + 1}`);
} }
if (item.name && item.phone && !validatePhone(item.phone)) {
return ElMessage.error(`请输入有效的手机号或座机号${i + 1}`);
}
} }
deviceConfigModify({ deviceConfigModify({
...ruleForm.value, ...ruleForm.value,

View File

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