电子围栏完成

This commit is contained in:
liub
2025-09-12 16:50:18 +08:00
parent e5d62c84a5
commit d976443fb9
8 changed files with 1594 additions and 3 deletions

View File

@ -6,8 +6,10 @@ VITE_APP_ENV = 'development'
# 开发环境 # 开发环境
# VITE_APP_BASE_API = 'http://47.120.79.150/backend' # VITE_APP_BASE_API = 'http://47.120.79.150/backend'
VITE_APP_BASE_API = 'http://192.168.2.23:8000' # VITE_APP_BASE_API = 'http://192.168.110.54:8000'
# VITE_APP_BASE_API = 'http://localhost:8000'
#代永飞接口
VITE_APP_BASE_API = 'http://457102h2d6.qicp.vip:24689'
# 应用访问路径 例如使用前缀 /admin/ # 应用访问路径 例如使用前缀 /admin/

View File

@ -6,7 +6,7 @@
<meta name="renderer" content="webkit" /> <meta name="renderer" content="webkit" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" /> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
<link rel="icon" href="/favicon.ico" /> <link rel="icon" href="/favicon.ico" />
<script src="https://webapi.amap.com/maps?v=2.0&key=90bc158992feb8ccd0145e168cab1307"></script> <script src="https://webapi.amap.com/maps?v=2.0&key=90bc158992feb8ccd0145e168cab1307&plugin=AMap.CircleEditor"></script>
<title>物联网管理平台</title> <title>物联网管理平台</title>
<!--[if lt IE 11 <!--[if lt IE 11
]><script> ]><script>

View File

@ -0,0 +1,77 @@
import request from '@/utils/request';
//修改电子围栏
function updateFence(data) {
return request({
url: '/api/equipment/geoFence',
method: 'put',
data: data
})
}
//新增电子围栏
function AddFence(data) {
return request({
url: '/api/equipment/geoFence',
method: 'post',
data: data
})
}
//导出电子围栏列表
function exportFence (data){
return request({
url: '/api/equipment/geoFence/export',
method: 'post',
data: data
})
}
//位置检查
function check(data) {
return request({
url: '/api/equipment/geoFence/check',
method: 'post',
data: data
})
}
//获取电子围栏详细信息
function geoFenceById(id) {
return request({
url: '/api/equipment/geoFence/'+id,
method: 'get'
})
}
//查询电子围栏列表
function getList(data) {
if(!data){
data={pageNum:1,pageSize:9999};
}
return request({
url: '/api/equipment/geoFence/list',
method: 'get',
params: data
})
}
//删除电子围栏
function DelFence(ids) {
return request({
url: '/api/equipment/geoFence/' + ids,
method: 'delete'
})
}
export default{
updateFence:updateFence,
AddFence:AddFence,
exportFence:exportFence,
check:check,
geoFenceById:geoFenceById,
getList:getList,
DelFence:DelFence
}

View File

@ -0,0 +1,240 @@
var map = null;
var circle = null;
var polygon = null;
function initMap() {
// let key = '90bc158992feb8ccd0145e168cab1307';
let init = function () {
map = new AMap.Map("map", {
viewMode: '2D', //默认使用 2D 模式
zoom: 11, //地图级别
center: [114.420739, 30.487514], //地图中心点
});
map.on('click',function(evt){
alert('您点击的位置:'+evt.lnglat.lng+' , '+ evt.lnglat.lat);
});
}
return new Promise((resolve, reject) => {
if(map){
resolve(200);
return;
}
if (window.AMap) {
init();
resolve(200);
return;
}
reject({code:500,msg:'高德地图未能初始化成功'});
});
}
//添加一个点
function AddPoint(point, index, dragEnd, click) {
return new Promise((resolve, reject) => {
try {
let center = point ? new AMap.LngLat(point.lng, point.lat) : map.getCenter();
let marker = new AMap.Text({
icon: "http://wdxm.ztzhtech.com:8111/Script/Home/img/welComeImg/mapLocation.png",
position: center,
offset: new AMap.Pixel(-15, -24),
draggable: true,
cursor: 'point',
title: '点击删除',
text: index,
class: 'point',
extData: { type: 'point', txt: index }
});
marker.setMap(map);
let lays = map.getAllOverlays('text');
for (let i = 0; i < lays.length; i++) {
const element = lays[i];
let cls = element.getOptions();
if (cls.class) {
element.dom.classList.add(cls.class);
}
}
marker.on('dragend', dragEnd);
marker.on('mouseover', function (evt) {
marker.setText('X');
});
marker.on('mouseout', function (evt) {
marker.setText(marker.getExtData().txt);
});
marker.on('click', function (evt) {
click(evt, marker);
});
resolve(center);
} catch (ex) {
reject(ex)
}
});
}
function getCenter() {
var center = map.getCenter().toJSON();
return center;
}
function setCenter(lon, lat) {
var position = new AMap.LngLat(lon, lat); //传入经纬度
map.setCenter(position); //简写 设置地图中心点
}
//画多边形
function DrawPoy(points) {
if(!map){
return;
}
if (!points) {
return;
}
let path = [];
if (polygon) {
map.remove(polygon);
polygon = null;
}
setTimeout(() => {
points.filter(v => {
path.push(new AMap.LngLat(v.lng, v.lat));
return true;
});
polygon = new AMap.Polygon({
path: path,
fillColor: '#ccebc5',
strokeOpacity: 1,
fillOpacity: 0.5,
strokeColor: '#FF0000',
strokeWeight: 5,
strokeStyle: 'solid',
strokeDasharray: [5, 5],
});
map.add(polygon);
// map.setFitView();
}, 0);
}
//画圆形
function DrawCicle(points, raduis, dragEnd) {
if(!map){
return;
}
if (circle) {
map.remove(circle);
}
circle = new AMap.Circle({
center: [points[0].lng, points[0].lat],
radius: raduis ? raduis : 1000, //半径
borderWeight: 3,
strokeColor: "#FF33FF",
strokeOpacity: 1,
strokeWeight: 6,
strokeOpacity: 0.2,
fillOpacity: 0.4,
strokeStyle: 'solid',
strokeDasharray: [10, 10],
// 线样式还支持 'dashed'
fillColor: '#1791fc',
zIndex: 50,
})
// var circleEditor = new AMap.CircleEditor(map, circle,{
// editOptions: {
// moveable: false, // 禁止拖拽圆心
// // 可以同时配置其他编辑选项
// // scalable: true // 允许缩放(默认开启)
// }
// });
// circleEditor.open();
// circleEditor.on('move', function (event) {
// console.log('触发事件move')
// })
// circleEditor.on('adjust', function (event) {
// console.log('触发事件adjust');
// dragEnd(event.radius);
// })
// circleEditor.on('end', function (event) {
// console.log('触发事件: end')
// // event.target 即为编辑后的圆形对象
// })
map.add(circle);
// 缩放地图到合适的视野级别
map.setFitView([circle])
}
//清除所有
function clearOverLays() {
map && map.clearMap();
}
function removeOverLay(lay) {
map && map.remove(lay);
}
function removePoy() {
if(!map){
return;
}
if (polygon) {
map.remove(polygon);
polygon = null;
}
}
function removeCircle() {
if(!map){
return;
}
if (circle) {
map.remove(circle);
circle = null;
}
}
function setFitView() {
if(map){map.setFitView();}
}
export default {
gdMap: map,
initMap: initMap,
AddPoint: AddPoint,
getCenter: getCenter,
DrawPoy: DrawPoy,
DrawCicle: DrawCicle,
clearOverLays: clearOverLays,
removeOverLay: removeOverLay,
removePoy: removePoy,
removeCircle: removeCircle,
setFitView: setFitView
}

View File

@ -0,0 +1,55 @@
import request from '@/utils/request';
//修改围栏进出记录
function updateRecord(data) {
return request({
url: '/api/equipment/fenceAccessRecord',
method: 'put',
data: data
})
}
//新增围栏进出记录
function addRecord(data) {
return request({
url: '/api/equipment/fenceAccessRecord',
method: 'post',
data: data
})
}
//获取围栏进出记录详细信息
function getRecordById(id) {
return request({
url: '/api/equipment/fenceAccessRecord/' + id,
method: 'get'
})
}
//查询围栏进出记录列表
function RecordList(data) {
return request({
url: '/api/equipment/fenceAccessRecord/list',
method: 'get',
params: data
})
}
//删除围栏进出记录
function DropRecord(ids) {
return request({
url: '/api/equipment/fenceAccessRecord/' + ids,
method: 'delete'
})
}
export default {
updateRecord: updateRecord,
addRecord: addRecord,
getRecordById: getRecordById,
RecordList: RecordList,
DropRecord: DropRecord
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@ -0,0 +1,593 @@
<template>
<div class="content" v-loading="Status.fullLoading">
<div class="percent100">
<div class="topTool">
<div class="button-row">
<el-button type="primary" @click.stop="ShowEdit(null)"
><el-icon><Plus /></el-icon>新增</el-button
>
<el-button type="primary" @click.stop="Export()" plain
><el-icon><Download /></el-icon>导出</el-button
>
<el-button type="danger" plain @click.stop="DropFence(null)"
><el-icon><Delete /></el-icon>删除</el-button
>
</div>
<div>
<el-form :inline="true" :model="queryParams" class="demo-form-inline" @submit.native.prevent>
<el-form-item label="" style="margin-right: 15px">
<el-input :suffix-icon="'Search'" v-model="queryParams.fenceName" placeholder="名称" clearable @keyup.enter.stop="handleQuery" />
</el-form-item>
<el-form-item style="margin-right: 0px">
<el-button type="primary" @click.stop="showSearch">高级筛选</el-button>
</el-form-item>
</el-form>
</div>
</div>
<div class="demo-collapse" v-show="Status.showSearch.length > 0">
<el-collapse accordion v-model="Status.showSearch">
<el-collapse-item name="1">
<el-form ref="queryFormRef" :model="queryParams" :inline="true" class="queryFormRef">
<el-form-item label="事件类型" prop="eventType">
<el-select v-model="queryParams.eventType" placeholder="请选择" clearable>
<el-option :key="1" :label="'闯入'" :value="1" />
<el-option :key="2" :label="'离开'" :value="2" />
</el-select>
</el-form-item>
<el-form-item label="事件时间" prop="Date">
<el-date-picker
style="width: 240px"
v-model="queryParams.Date"
type="daterange"
range-separator="-"
start-placeholder="开始时间"
end-placeholder="结束时间"
/>
</el-form-item>
<el-form-item label="围栏名称" prop="fenceId">
<el-select v-model="queryParams.fenceId" placeholder="请选择" clearable filterable>
<el-option v-for="item in dic.fence" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="设备名称" prop="deviceId">
<el-select v-model="queryParams.deviceId" placeholder="请选择" clearable filterable>
<el-option v-for="item in dic.device" :key="item.deviceId" :label="item.deviceName" :value="item.deviceId" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleQuery">查询</el-button>
<el-button @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-collapse-item>
</el-collapse>
</div>
<el-table
ref="grid"
v-loading="Status.loading"
border
:data="List"
:height="Status.showSearch.length > 0 ? 'calc(100vh - 355px)' : 'calc(100vh - 255px)'"
>
<el-table-column type="selection" width="50" align="center" />
<el-table-column label="围栏名称" align="center" prop="fenceName"> </el-table-column>
<el-table-column label="设备名称" align="center" prop="deviceName"> </el-table-column>
<!-- <el-table-column label="人员名称" align="center" prop="userName"> </el-table-column> -->
<el-table-column label="事件类型" align="center" prop="eventType">
<template #default="scope">
<div>{{ scope.row.eventType == '1' ? '闯入' : '离开' }}</div>
</template>
</el-table-column>
<el-table-column label="经纬度" align="center" prop="id">
<template #default="scope">
<span style="margin-right: 15px">{{ scope.row.longitude }}</span
><span>{{ scope.row.latitude }}</span>
</template>
</el-table-column>
<el-table-column label="精度" align="userName" prop="accuracy"> </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="ShowEdit(scope.row)">编辑</el-button>
<el-button link type="danger" @click="DropFence(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="pagin.total > 0"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
:total="pagin.total"
@pagination="getList"
/>
</div>
<!-- 编辑弹出层 -->
<el-dialog width="760px" v-model="Status.showEdit" :title="'编辑记录'" :draggable="true">
<div>
<el-form :inline="true" :model="cEdit" label-width="auto">
<el-form-item label="围栏名称">
<el-select v-model="cEdit.fenceId" placeholder="请选择" clearable filterable>
<el-option v-for="item in dic.fence" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="设备名称">
<el-select v-model="cEdit.deviceId" placeholder="请选择" clearable filterable>
<el-option v-for="item in dic.device" :key="item.deviceId" :label="item.deviceName" :value="item.deviceId" />
</el-select>
</el-form-item>
<!-- <el-form-item label="人员名称">
<el-select v-model="cEdit.userId" placeholder="请选择">
<el-option :key="0" :label="'多边形'" :value="0" />
<el-option :key="1" :label="'圆形'" :value="1" />
</el-select>
</el-form-item> -->
<el-form-item label="事件时间">
<el-date-picker v-model="cEdit.eventTime" type="datetime" placeholder="选择时间" />
</el-form-item>
<el-form-item label="事件类型">
<el-select v-model="cEdit.eventType" placeholder="请选择" clearable>
<el-option :key="1" :label="'闯入'" :value="1" />
<el-option :key="2" :label="'离开'" :value="2" />
</el-select>
</el-form-item>
<el-form-item label="经度">
<el-input v-model="cEdit.longitude" />
</el-form-item>
<el-form-item label="纬度">
<el-input v-model="cEdit.latitude" />
</el-form-item>
<el-form-item label="精度">
<el-input v-model="cEdit.accuracy" />
</el-form-item>
</el-form>
</div>
<div class="center" style="margin-top: 20px">
<el-button type="primary" @click="SaveFormData"> 确定 </el-button>
<el-button type="primary" plain @click="CloseEdit"> 取消 </el-button>
</div>
</el-dialog>
<!-- 提示框 -->
<el-dialog :width="300" :draggable="true" v-model="Status.confirm.Visible" :title="Status.confirm.title" center>
<span :class="Status.confirm.isWarn">
{{ Status.confirm.text }}
</span>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="Status.confirm.OkCallback"> 确定 </el-button>
<el-button v-show="Status.confirm.showCancel" @click="Status.confirm.cancelCallback">取消</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import api from '@/api/FenceManager/record';
import fenceApi from '@/api/FenceManager/fence';
import common from '@/utils/common';
import deviceapi from '@/api/equipmentManagement/device/index';
import request from '@/utils/request';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
var grid = ref(null);
//全局状态控制
var Status = reactive({
fullLoading: false, //是否显示全屏loading
loading: false, //是否显示表格区域loading
confirm: {
//弹出框的配置
Visible: false,
title: '',
text: '',
cancelCallback: null,
OkCallback: null,
showCancel: true,
isWarn: false //是否警告色
},
showSearch: [], //是否显示高级查询
showEdit: false //是否显示编辑弹窗
});
//查询的条件
var queryParams = reactive({
fenceName: '',
fenceId: '',
deviceId: '',
eventType: '',
latitude: '',
longitude: '',
accuracy: '',
Date: [],
startEventTime: '',
endEventTime: '',
pageNum: 1,
pageSize: 10
});
var dic = reactive({
fence: [],
device: [],
users: []
});
var cEdit = reactive({
'id': null,
'fenceId': null,
'deviceId': null,
'userId': null,
'eventType': null,
'latitude': null,
'longitude': null,
'accuracy': null,
'eventTime': null
});
//页码控件数据
var pagin = reactive({
total: 0
});
//列表数据
var List = ref<any[]>(null);
//显示隐藏高级查询
function showSearch() {
if (Status.showSearch.length > 0) {
Status.showSearch = [];
} else {
Status.showSearch = ['1'];
}
}
function ShowEdit(item) {
Status.showEdit = true;
if (item) {
let keys = Object.keys(item);
keys.forEach((key) => {
cEdit[key] = item[key];
});
}
}
function CloseEdit() {
Status.showEdit = false;
let defCfg = {
'id': null,
'fenceId': null,
'deviceId': null,
'userId': null,
'eventType': null,
'latitude': null,
'longitude': null,
'accuracy': null,
'eventTime': null
};
let keys = Object.keys(defCfg);
keys.forEach((key) => {
cEdit[key] = defCfg[key];
});
}
//响应查询
var queryTime = null;
function handleQuery() {
clearTimeout(queryTime);
queryTime = setTimeout(getList, 500);
}
//重置查询条件
function resetQuery() {
let cfg = {
fenceName: '',
fenceId: '',
deviceId: '',
eventType: '',
latitude: '',
longitude: '',
accuracy: '',
Date: [],
startEventTime: '',
endEventTime: ''
};
let keys = Object.keys(cfg);
keys.forEach((k) => {
queryParams[k] = cfg[k];
});
}
//--------------------------------------
//保存数据
function SaveFormData() {
let promise = Promise.resolve({ code: 200, msg: '操作成功' });
Status.fullLoading = true;
if (cEdit.id !== null) {
//编辑
promise = api.updateRecord(cEdit);
} else {
//新增
promise = api.addRecord(cEdit);
}
promise
.then((res) => {
if (res.code == 200) {
CloseEdit();
getList();
}
alert(res.msg);
})
.finally(() => {
Status.fullLoading = false;
});
}
//删除围栏
function DropFence(item) {
let arr = [];
if (item) {
arr = [item];
} else {
arr = getSelectionRows(grid);
}
if (arr.length == 0) {
alert('请选择要删除的数据');
return;
}
confirm(
'删除后不可恢复,您确认?',
() => {
arr = arr.map((v) => {
return v.id;
});
api.DropRecord(arr).then((res) => {
if (res.code == 200) {
handleQuery();
}
alert(res.msg);
});
},
null,
'提示'
);
}
function getList() {
Status.loading = true;
api
.RecordList(queryParams)
.then((res) => {
List.value = res.rows;
pagin.total = res.total;
})
.finally(() => {
Status.loading = false;
});
}
function Export() {
let json = {};
proxy?.download('/api/equipment/fenceAccessRecord/export', json, `围栏进出记录_${new Date().getTime()}.xlsx`, 'post').finally(() => {
});
}
//获取已选中的行
var getSelectionRows = (gridInstance) => {
if (gridInstance.value) {
// 检查ref是否已正确引用组件实例
var selectedRows = gridInstance.value.getSelectionRows(); // 获取选中行数据数组
return selectedRows;
}
return [];
};
//字典初始化
function initDic() {
let getFence = () => {
fenceApi.getList(null).then((res) => {
if (res.code == 200) {
dic.fence = res.rows;
}
});
};
let getDevice = () => {
request({
url: '/api/device',
method: 'get',
params: { pageNum: 1, pageSize: 9999 }
}).then((res) => {
if (res.code == 200) {
dic.device = res.rows;
}
});
};
let getUsers = () => {
// request({
// url: '/system/user/list',
// method: 'get',
// params: { pageNum: 1, pageSize: 9999 }
// }).then((res) => {
// if (res.code == 200) {
// dic.users = res.rows;
// }
// });
};
getFence();
getDevice();
getUsers();
}
window.confirm = function (text, OK, cancel, title, isWarn = false) {
let Cfg = {
Visible: true,
title: title ? title : '提示',
text: text ? text : '此操作不可逆,您确定这样做吗?',
OkCallback: () => {
Status.confirm.Visible = false;
if (OK) {
OK();
}
},
showCancel: true,
cancelCallback: () => {
Status.confirm.Visible = false;
if (cancel) {
cancel();
}
},
isWarn: isWarn
};
Status.confirm = Cfg;
};
window.alert = function (text, OK, title) {
let Cfg = {
Visible: true,
title: title ? title : '提示',
text: text ? text : '不符合规则',
OkCallback: () => {
Status.confirm.Visible = false;
if (OK) {
OK();
}
},
showCancel: false,
cancelCallback: null,
isWarn: false
};
Status.confirm = Cfg;
};
var hideConfirm = function () {
let Cfg = {
Visible: false,
title: '提示',
text: '',
cancelCallback: null,
OkCallback: null,
showCancel: false,
isWarn: false
};
Status.confirm = Cfg;
};
onMounted(() => {
getList();
initDic();
});
</script>
<style lang="scss" scoped>
.fleft {
float: left;
}
.fright {
float: right;
}
.clear {
clear: both;
}
.center {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
align-content: center;
align-items: center;
justify-content: center;
}
.displayNone {
display: none !important;
}
.content {
width: 100%;
min-height: calc(100vh - 84px);
height: calc(100vh - 84px);
background: rgba(247, 248, 252, 1);
font-size: 16px;
box-sizing: border-box;
padding: 8px 20px;
font-family: 'Microsoft YaHei';
}
.topTool {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
align-content: center;
justify-content: space-between;
align-items: flex-start;
width: 100%;
border-bottom: 1px solid #e1e2e5f2;
margin-bottom: 15px;
}
.percent100 {
width: 100%;
height: 100%;
border-radius: 4px;
box-shadow: 0px 0px 6px 0px rgba(0, 34, 96, 0.1);
background: rgba(255, 255, 255, 1);
box-sizing: border-box;
padding: 15px;
}
:deep .el-collapse-item__header {
display: none !important;
}
:deep .el-collapse,
:deep .el-collapse-item__wrap {
border: none !important;
}
.map {
width: 100%;
height: 58vh;
box-sizing: border-box;
border: 1px dashed red;
}
.point,
:deep .point {
background-image: url(@/assets/images/mapLocation.png);
width: 30px;
height: 48px;
background-repeat: no-repeat;
background-color: #00000000;
background-size: cover;
}
.point amap-overlay-text-container,
:deep .point .amap-overlay-text-container {
color: #ffffff;
font-size: 17px;
border: none;
text-align: center;
line-height: 30px;
background: #00000000;
}
.el-date-editor,
:deep .el-date-editor {
width: 240px !important;
}
</style>

View File

@ -0,0 +1,624 @@
<template>
<div class="content" v-loading="Status.fullLoading">
<div class="percent100">
<div class="topTool">
<div class="button-row">
<el-button type="primary" @click.stop="ShowEdit(null)"
><el-icon><Plus /></el-icon>新增</el-button
>
<el-button type="primary" @click.stop="Export()" plain
><el-icon><Download /></el-icon>导出</el-button
>
<el-button type="danger" plain @click.stop="DropFence(null)"
><el-icon><Delete /></el-icon>删除</el-button
>
</div>
<div>
<el-form :inline="true" :model="queryParams" class="demo-form-inline" @submit.native.prevent>
<el-form-item label="" style="margin-right: 15px">
<el-input :suffix-icon="'Search'" v-model="queryParams.name" placeholder="名称" clearable @keyup.enter.stop="handleQuery" />
</el-form-item>
<el-form-item style="margin-right: 0px">
<el-button type="primary" @click.stop="showSearch">高级筛选</el-button>
</el-form-item>
</el-form>
</div>
</div>
<div class="demo-collapse" v-show="Status.showSearch.length > 0">
<el-collapse accordion v-model="Status.showSearch">
<el-collapse-item name="1">
<el-form ref="queryFormRef" :model="queryParams" :inline="true" class="queryFormRef">
<el-form-item label="围栏类型" prop="areaType">
<el-select v-model="queryParams.areaType" placeholder="请选择" clearable>
<el-option :key="0" :label="'多边形'" :value="0" />
<el-option :key="1" :label="'圆形'" :value="1" />
</el-select>
</el-form-item>
<el-form-item label="是否激活" prop="isActive" >
<el-select v-model="queryParams.isActive" clearable placeholder="请选择">
<el-option :key="1" :label="'是'" :value="1" />
<el-option :key="0" :label="'否'" :value="0" />
</el-select>
</el-form-item>
<el-form-item label="描述" prop="description">
<el-input v-model="queryParams.description" placeholder="请输入设备名称" clearable />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleQuery">查询</el-button>
<el-button @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-collapse-item>
</el-collapse>
</div>
<el-table
ref="grid"
v-loading="Status.loading"
border
:data="List"
:height="Status.showSearch.length > 0 ? 'calc(100vh - 355px)' : 'calc(100vh - 255px)'"
>
<el-table-column type="selection" width="50" align="center" />
<el-table-column label="围栏名称" align="center" prop="name" />
<el-table-column label="围栏类型" align="center" prop="areaType">
<template #default="scope">
<div>{{ scope.row.areaType == '1' ? '圆形' : '多边形' }}</div>
</template>
</el-table-column>
<el-table-column label="半径(米)" align="center" prop="radius"> </el-table-column>
<el-table-column label="是否激活" align="center" prop="isActive">
<template #default="scope">
<div>{{ scope.row.isActive == '1' ? '是' : '否' }}</div>
</template>
</el-table-column>
<el-table-column label="描述" align="center" prop="description"> </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="ShowEdit(scope.row)">编辑</el-button>
<el-button link type="danger" @click="DropFence(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="pagin.total > 0"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
:total="pagin.total"
@pagination="getList"
/>
</div>
<!-- 围栏编辑弹出层 -->
<el-dialog width="calc(calc(100% - 250px) * 0.9)" v-model="Status.showEdit" :title="'编辑围栏'" :draggable="true">
<div>
<el-form :inline="true" :model="cEdit" label-width="auto">
<el-form-item label="围栏名称">
<el-input v-model="cEdit.name" />
</el-form-item>
<el-form-item label="围栏类型">
<el-select v-model="cEdit.areaType" placeholder="请选择" @change="areaTypeChange()">
<el-option :key="0" :label="'多边形'" :value="0" />
<el-option :key="1" :label="'圆形'" :value="1" />
</el-select>
</el-form-item>
<el-form-item label="半径(米)" :class="cEdit.areaType == 1 ? '' : 'displayNone'">
<el-input v-model="cEdit.radius" @keydown="refreshOverLayer" />
</el-form-item>
<el-form-item label="是否激活">
<el-select v-model="cEdit.isActive" placeholder="请选择">
<el-option :key="1" :label="'是'" :value="1" />
<el-option :key="0" :label="'否'" :value="0" />
</el-select>
</el-form-item>
<el-form-item label="备注" style="width: 350px">
<el-input v-model="cEdit.description" type="textarea" />
</el-form-item>
</el-form>
</div>
<div>
<el-button type="primary" @click.stop="AddPoint()"
><el-icon><Plus /></el-icon>添加坐标</el-button
>
<el-button type="primary" @click.stop="ClearPoint()">
<el-icon><Delete /></el-icon>清空</el-button
>
</div>
<div class="map" id="map"></div>
<div class="center" style="margin-top: 20px">
<el-button type="primary" @click="SaveFormData"> 确定 </el-button>
<el-button type="primary" plain @click="CloseEdit"> 取消 </el-button>
</div>
</el-dialog>
<!-- 提示框 -->
<el-dialog :width="300" :draggable="true" v-model="Status.confirm.Visible" :title="Status.confirm.title" center>
<span :class="Status.confirm.isWarn">
{{ Status.confirm.text }}
</span>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="Status.confirm.OkCallback"> 确定 </el-button>
<el-button v-show="Status.confirm.showCancel" @click="Status.confirm.cancelCallback">取消</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import api from '@/api/FenceManager/fence';
import common from '@/utils/common';
import map from '@/api/FenceManager/mapOpt';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
var grid = ref(null);
//全局状态控制
var Status = reactive({
fullLoading: false, //是否显示全屏loading
loading: false, //是否显示表格区域loading
confirm: {
//弹出框的配置
Visible: false,
title: '',
text: '',
cancelCallback: null,
OkCallback: null,
showCancel: true,
isWarn: false //是否警告色
},
showSearch: [], //是否显示高级查询
showEdit: false //是否显示编辑弹窗
});
//查询的条件
var queryParams = reactive({
name: '',
areaType: null,
isActive: null,
description: '',
pageNum: 1,
pageSize: 10
});
var cEdit = reactive({
id: null,
name: '',
description: '',
areaType: null,
coordinates: [],
radius: null,
isActive: null
});
//页码控件数据
var pagin = reactive({
total: 0
});
//列表数据
var List = ref<any[]>(null);
//显示隐藏高级查询
function showSearch() {
if (Status.showSearch.length > 0) {
Status.showSearch = [];
} else {
Status.showSearch = ['1'];
}
}
function ShowEdit(item) {
Status.showEdit = true;
if (item) {
let keys = Object.keys(item);
keys.forEach((key) => {
if (key !== 'coordinates') {
cEdit[key] = item[key];
}
});
if (item.coordinates) {
cEdit.coordinates = JSON.parse(item.coordinates);
}
}
map.clearOverLays();
setTimeout(() => {
map
.initMap()
.then((res) => {
console.log('地图初始化成功', res);
if (cEdit.coordinates.length > 0) {
refreshOverLayer();
for (let i = 0; i < cEdit.coordinates.length; i++) {
let point = cEdit.coordinates[i];
AddPoint(point, i);
}
map.setFitView();
}
})
.catch(() => {
alert('地图初始化没有成功');
});
}, 500);
}
function CloseEdit() {
Status.showEdit = false;
let defCfg = {
id: null,
name: '',
description: '',
areaType: null,
coordinates: [],
radius: null,
isActive: null
};
let keys = Object.keys(defCfg);
keys.forEach((key) => {
cEdit[key] = defCfg[key];
});
}
//响应查询
var queryTime = null;
function handleQuery() {
clearTimeout(queryTime);
queryTime = setTimeout(getList, 500);
}
//重置查询条件
function resetQuery() {
let cfg = {
name: '',
areaType: null,
isActive: null,
description: ''
};
let keys = Object.keys(cfg);
keys.forEach((k) => {
queryParams[k] = cfg[k];
});
}
//--------------------------------------
//修改围栏类型时,清空所有坐标
function areaTypeChange() {
cEdit.coordinates = [];
map.clearOverLays();
}
//圆大小改变时,更新半径字段
function circleDragEnd(radius) {
cEdit.radius = radius;
}
//修改半径后主动更新图形
let time = null;
function refreshOverLayer() {
clearTimeout(time);
time = setTimeout(() => {
if (cEdit.areaType == 1) {
map.DrawCicle(cEdit.coordinates, cEdit.radius, circleDragEnd);
} else {
map.DrawPoy(cEdit.coordinates);
}
}, 200);
}
//清空所有点
function ClearPoint() {
confirm(
'您确认清除所有标记吗?',
() => {
cEdit.coordinates = [];
map.clearOverLays();
},
null,
'提示'
);
}
//添加一个点
function AddPoint(point, i) {
if (cEdit.areaType == null && !point) {
alert('请选择围栏类型');
return;
}
if (!point && cEdit.areaType == 1 && cEdit.coordinates.length > 0) {
alert('圆形围栏只有一个中心坐标点');
return;
}
let index = i !== undefined ? i : cEdit.coordinates.length;
let dragEnd = (evt, lay) => {
map.removePoy();
cEdit.coordinates[index].lng = evt.lnglat.lng;
cEdit.coordinates[index].lat = evt.lnglat.lat;
refreshOverLayer();
};
let pointClick = (evt, lay) => {
confirm(
'您确认删除该点吗?',
() => {
cEdit.coordinates.splice(index, 1);
map.removeOverLay(lay);
refreshOverLayer();
},
null,
null
);
};
map.AddPoint(point, index + 1, dragEnd, pointClick).then((res) => {
let lon = res.lng;
let lat = res.lat;
if(!point){cEdit.coordinates.push({ lng: lon, lat: lat });}
refreshOverLayer();
});
}
//保存数据
function SaveFormData() {
if (cEdit.areaType == 0 && cEdit.coordinates.length < 3) {
alert('多边形的围栏至少需要3个点');
return;
}
if (cEdit.areaType == 0 && cEdit.coordinates.length == 0) {
alert('圆形的围栏至少需要1个点');
return;
}
let promise = Promise.resolve({ code: 200, msg: '操作成功' });
Status.fullLoading = true;
let json = Object.assign({}, cEdit);
json.coordinates = JSON.stringify(json.coordinates);
if (cEdit.id !== null) {
//编辑
promise = api.updateFence(json);
} else {
//新增
promise = api.AddFence(json);
}
promise
.then((res) => {
if (res.code == 200) {
getList();
CloseEdit();
}
alert(res.msg);
})
.finally(() => {
Status.fullLoading = false;
});
}
//删除围栏
function DropFence(item) {
let arr = [];
if (item) {
arr = [item];
} else {
arr = getSelectionRows(grid);
}
if (arr.length == 0) {
alert('请选择要删除的数据');
return;
}
arr = arr
.map((v) => {
return v.id;
})
.join(',');
api.DelFence(arr).then((res) => {
if (res.code == 200) {
handleQuery();
}
alert(res.msg);
});
}
function getList() {
Status.loading = true;
api
.getList(queryParams)
.then((res) => {
List.value = res.rows;
pagin.total = res.total;
})
.finally(() => {
Status.loading = false;
});
}
function Export() {
let json = {};
proxy?.download('/api/equipment/geoFence/export', json, `电子围栏列表_${new Date().getTime()}.xlsx`, 'post').finally(() => {
});
}
//获取已选中的行
var getSelectionRows = (gridInstance) => {
if (gridInstance.value) {
// 检查ref是否已正确引用组件实例
var selectedRows = gridInstance.value.getSelectionRows(); // 获取选中行数据数组
return selectedRows;
}
return [];
};
window.confirm = function (text, OK, cancel, title, isWarn = false) {
let Cfg = {
Visible: true,
title: title ? title : '提示',
text: text ? text : '此操作不可逆,您确定这样做吗?',
OkCallback: () => {
Status.confirm.Visible = false;
if (OK) {
OK();
}
},
showCancel: true,
cancelCallback: () => {
Status.confirm.Visible = false;
if (cancel) {
cancel();
}
},
isWarn: isWarn
};
Status.confirm = Cfg;
};
window.alert = function (text, OK, title) {
let Cfg = {
Visible: true,
title: title ? title : '提示',
text: text ? text : '不符合规则',
OkCallback: () => {
Status.confirm.Visible = false;
if (OK) {
OK();
}
},
showCancel: false,
cancelCallback: null,
isWarn: false
};
Status.confirm = Cfg;
};
var hideConfirm = function () {
let Cfg = {
Visible: false,
title: '提示',
text: '',
cancelCallback: null,
OkCallback: null,
showCancel: false,
isWarn: false
};
Status.confirm = Cfg;
};
onMounted(() => {
getList();
});
</script>
<style lang="scss" scoped>
.fleft {
float: left;
}
.fright {
float: right;
}
.clear {
clear: both;
}
.center {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
align-content: center;
align-items: center;
justify-content: center;
}
.displayNone {
display: none !important;
}
.content {
width: 100%;
min-height: calc(100vh - 84px);
height: calc(100vh - 84px);
background: rgba(247, 248, 252, 1);
font-size: 16px;
box-sizing: border-box;
padding: 8px 20px;
font-family: 'Microsoft YaHei';
}
.topTool {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
align-content: center;
justify-content: space-between;
align-items: flex-start;
width: 100%;
border-bottom: 1px solid #e1e2e5f2;
margin-bottom: 15px;
}
.percent100 {
width: 100%;
height: 100%;
border-radius: 4px;
box-shadow: 0px 0px 6px 0px rgba(0, 34, 96, 0.1);
background: rgba(255, 255, 255, 1);
box-sizing: border-box;
padding: 15px;
}
:deep .el-collapse-item__header {
display: none !important;
}
:deep .el-collapse,
:deep .el-collapse-item__wrap {
border: none !important;
}
.map {
width: 100%;
height: 58vh;
box-sizing: border-box;
border: 1px solid #7371719e;
border-radius: 8px;
margin-top: 10px;
}
.point,
:deep .point {
background-image: url(http://wdxm.ztzhtech.com:8111/Script/Home/img/welComeImg/mapLocation.png);
width: 30px;
height: 48px;
background-repeat: no-repeat;
background-color: #00000000;
background-size: cover;
}
.point amap-overlay-text-container,
:deep .point .amap-overlay-text-container {
color: #ffffff;
font-size: 17px;
border: none;
text-align: center;
line-height: 30px;
background: #00000000;
}
.point:hover,
:deep .point:hover {
filter: hue-rotate(169deg);
}
</style>