2025年05月20日14:28:51
This commit is contained in:
parent
b0f807597d
commit
22382d746e
1
components.d.ts
vendored
1
components.d.ts
vendored
@ -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']
|
||||
|
126
src/components/locationList.vue
Normal file
126
src/components/locationList.vue
Normal 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>
|
@ -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);
|
||||
});
|
||||
|
@ -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()
|
||||
|
@ -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: "处理状态" },
|
||||
|
@ -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 });
|
||||
}
|
||||
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 });
|
||||
}
|
||||
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>
|
||||
|
@ -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 });
|
||||
}
|
||||
|
@ -164,6 +164,7 @@ const viewHistory = (row: TDevice.IUseRecordRes) => {
|
||||
};
|
||||
onUnmounted(() => {
|
||||
sessionStorage.removeItem("query");
|
||||
console.log(111);
|
||||
});
|
||||
</script>
|
||||
|
||||
|
@ -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,31 +65,32 @@ 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 });
|
||||
}
|
||||
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(() => {
|
||||
try {
|
||||
@ -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;
|
||||
|
@ -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,14 +148,12 @@ 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) => {
|
||||
return {
|
||||
name: item.name,
|
||||
phone: item.phone,
|
||||
};
|
||||
});
|
||||
let arr = res.contacts.map((item) => {
|
||||
return {
|
||||
name: item.name,
|
||||
phone: item.phone,
|
||||
};
|
||||
});
|
||||
ruleForm.value = {
|
||||
...res,
|
||||
contacts: arr.length
|
||||
@ -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,
|
||||
|
@ -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) => {
|
||||
|
Loading…
x
Reference in New Issue
Block a user