Files
dyf-vue-ui/src/views/fys-equipment/geoFence/index.vue
2025-11-28 17:06:06 +08:00

1409 lines
38 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="content" v-loading="Status.fullLoading">
<div class="percent100" v-show="!Status.showEdit && !Status.showDevice">
<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 @input="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="primary" @click="ShowDevice(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>
<!-- 围栏编辑弹出层 -->
<div class="editLayer percent100 popLayer" v-show="Status.showEdit" :title="'编辑围栏'">
<div class="abClose" @click="CloseEdit">
<el-icon><Close /></el-icon>
</div>
<div>
<el-form :inline="true" :model="cEdit" label-width="auto">
<el-form-item label="服务id">
<el-select v-model="cEdit.sid" placeholder="请选择" :disabled="cEdit.id !== null">
<el-option v-for="(item, index) in services" :key="item.sid" :label="item.sname" :value="item.sid" />
</el-select>
</el-form-item>
<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="半径(米)">
<el-input v-model="cEdit.radius" @keydown="refreshOverLayer" :disabled="cEdit.areaType !== 1" />
</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="备注">
<el-input v-model="cEdit.description" />
</el-form-item>
</el-form>
</div>
<div>
<el-button type="primary" @click.stop="AddPoint()"
><el-icon><Plus /></el-icon>添加点</el-button
>
<el-button type="danger" @click.stop="ClearPoint()">
<el-icon><Delete /></el-icon>清空</el-button
>
</div>
<div class="map" id="map"></div>
<div class="center footerBtn" style="margin-top: 20px">
<el-button type="primary" @click="SaveFormData"> 确定 </el-button>
<el-button type="danger" plain @click="CloseEdit"> 关闭 </el-button>
</div>
</div>
<!-- 终端维护的弹窗 -->
<div class="percent100 deviceLayer popLayer" v-show="Status.showDevice">
<div class="abClose" @click="CloseDevice">
<el-icon><Close /></el-icon>
</div>
<div class="gridContent">
<div class="button-row">
<el-button type="primary" @click="showCheckDevice" class="fleft"
><el-icon><Plus /></el-icon>添加</el-button
>
<el-button type="danger" @click="DropDevice(null)" class="fleft"
><el-icon><Delete /></el-icon>删除</el-button
>
<div class="fright">
<el-form :inline="true">
<el-form-item label="" prop="checkedKey">
<el-input v-model="searchFilter.nocheckKey" placeholder="imei查询" clearable @keyup.enter="SearchDevice" />
</el-form-item>
<!-- <el-form-item>
<el-button type="primary" icon="Search" @click="SearchDevice">搜索</el-button>
</el-form-item> -->
</el-form>
</div>
<div class="clear"></div>
</div>
<el-table ref="gridDevice" v-loading="Status.deviceLoading" border :data="DeviceList" @selection-change="ZDSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<!-- <el-table-column label="deviceId" align="center" prop="deviceId" />
<el-table-column label="id" align="center" prop="id" /> -->
<el-table-column label="设备类型" align="center" prop="typeName" />
<el-table-column label="设备名称" align="center" prop="deviceName" />
<el-table-column label="IMEI" align="center" prop="deviceImei" />
<el-table-column label="Mac" align="center" prop="deviceMac" />
<el-table-column label="蓝牙名称" align="center" prop="bluetoothName" />
<!-- <el-table-column label="高德服务id" align="center" prop="sid" /> -->
<el-table-column label="高德终端id" align="center" prop="tid" />
<el-table-column label="高德轨迹id" align="center" prop="trid" />
<el-table-column label="操作" fixed="right" width="180" class-name="small-padding fixed-width">
<template #default="scope">
<el-icon color="#FF0000" @click="DropDevice(scope.row)"><Delete /></el-icon>
</template>
</el-table-column>
</el-table>
<pagination
v-show="devicePage.total > 0"
v-model:page="devicePage.pageNum"
v-model:limit="devicePage.pageSize"
:total="devicePage.total"
@pagination="getList"
/>
</div>
<div class="center footerBtn" style="margin-top: 20px">
<el-button type="danger" plain @click="CloseDevice"> 关闭 </el-button>
</div>
</div>
<el-dialog :title="'选择终端'" :draggable="true" footer-class="dilogFooter" v-model="Status.showCheckDevice" width="65%" append-to-body>
<div class="deviceContent">
<div class="filter">
<el-form :inline="true">
<el-form-item label="关键字" prop="checkedKey">
<el-input v-model="searchFilter.checkedKey" placeholder="imei查询" clearable @keyup.enter="searchZD" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="searchZD">搜索</el-button>
</el-form-item>
</el-form>
</div>
<div class="checked">
<el-table
v-loading="Status.showCheckDeviceLoading"
border
:data="checkedDevices"
@selection-change="handleSelectionChange"
ref="multipleTableRef"
row-key="id"
>
<el-table-column type="selection" width="55" align="center" />
<!-- <el-table-column label="deviceId" align="center" prop="deviceId" />
<el-table-column label="id" align="center" prop="id" /> -->
<el-table-column label="设备类型" align="center" prop="typeName" />
<el-table-column label="设备名称" align="center" prop="deviceName" />
<el-table-column label="IMEI" align="center" prop="deviceImei" />
<el-table-column label="Mac" align="center" prop="deviceMac" />
<el-table-column label="蓝牙名称" align="center" prop="bluetoothName" />
<!-- <el-table-column label="高德服务id" align="center" prop="sid" /> -->
<el-table-column label="高德终端id" align="center" prop="tid" />
<el-table-column label="高德轨迹id" align="center" prop="trid" />
</el-table>
<pagination
:total="checkedPageParams.total"
v-model:page="checkedPageParams.pageNum"
v-model:limit="checkedPageParams.pageSize"
@pagination="searchZD"
/>
</div>
</div>
<template #footer>
<div class="center footerBtn" style="margin-top: 20px">
<el-button type="primary" @click="SaveChecked"> 确定 </el-button>
<el-button type="danger" plain @click="CloseCheckDevice"> 关闭 </el-button>
</div>
</template>
</el-dialog>
<!-- 提示框 -->
<el-dialog :width="300" :draggable="true" v-model="Status.confirm.Visible" :title="Status.confirm.title" center>
<span :class="Status.confirm.isWarn" v-html="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';
import { listService } from '@/api/system/service/api';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
import { getDevice } from '@/api/system/service/api';
const mapApiKey = import.meta.env.VITE_AMAP_KEY;
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, //是否显示编辑弹窗
showDevice: false, //是否显示终端维护
deviceLoading: false, //终端查询的loading
showCheckDevice: false, //是否显示选择终端
showCheckDeviceLoading: 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: undefined,
isActive: null,
sid: null,
gfid: null
});
var checkGf = reactive({
//当前正在操作的数据
sid: null,
gfid: null
});
//页码控件数据
var pagin = reactive({
total: 0
});
var devicePage = reactive({
total: 0,
pageNum: 1,
pageSize: 10
});
var checkedPageParams = reactive({
pageNum: 1,
pageSize: 20,
total: 0
});
//列表数据
var List = ref<any[]>(null);
var services = ref<any[]>(null);
var DeviceList = ref<any[]>(null);
const checkZd = ref<any>([]);
var checkedDevices = ref([]);
const searchFilter = ref({
nocheckKey: '',
checkedKey: ''
});
const ids = ref<any>([]);
//显示终端
function ShowDevice(item) {
Status.showDevice = true;
checkGf = item;
SearchDevice();
}
//选择终端
function ZDSelectionChange(selection: any[]) {
checkZd.value = selection;
}
//查询已选择的终端
function SearchDevice() {
Status.deviceLoading = true;
let searchMap = () => {
let json = {
key: mapApiKey,
sid: checkGf.sid,
gfid: checkGf.gfid
};
let formData = [];
if (json) {
let keys = Object.keys(json);
for (let index = 0; index < keys.length; index++) {
let key = keys[index];
let encodedKey = encodeURIComponent(key);
let encodedValue = encodeURIComponent(json[key]);
formData.push(`${encodedKey}=${encodedValue}`);
}
}
let queryString = formData.join('&'); // 拼接成 key1=value1&key2=value2
request({
url: 'https://tsapi.amap.com/v1/track/geofence/terminal/list?' + queryString,
method: 'GET',
data: ''
})
.then((res) => {
if (res && res.errcode === 10000) {
DeviceList.value = res.data.results;
devicePage.total = res.data.count;
}
})
.finally(() => {
Status.deviceLoading = false;
});
};
let searchSys = () => {
let json = {
fenceId: checkGf.gfid,
sid: checkGf.sid,
isTid: true,
pageNum: devicePage.pageNum,
pageSize: devicePage.pageSize,
deviceImei: searchFilter.value.nocheckKey
};
api
.pageTerminal(json)
.then((res) => {
if (res && res.code === 200) {
DeviceList.value = res.rows;
devicePage.total = res.total;
return;
}
DeviceList.value = [];
devicePage.total = 0;
})
.finally(() => {
Status.deviceLoading = false;
});
};
searchSys();
}
//关闭终端
function CloseDevice() {
Status.showDevice = false;
}
//显示选择终端
function showCheckDevice() {
Status.showCheckDevice = true;
searchZD();
}
//查询待选择的终端
function searchZD() {
return new Promise((resolve, reject) => {
let json = {
fenceId: checkGf.gfid,
sid: checkGf.sid,
isTid: false,
pageNum: checkedPageParams.pageNum,
pageSize: checkedPageParams.pageSize,
deviceImei: searchFilter.value.checkedKey
};
api
.pageTerminal(json)
.then((res) => {
if (res.code === 200) {
checkedDevices.value = res.rows;
checkedPageParams.total = res.total;
resolve(res);
return;
}
resolve(null);
})
.catch((ex) => {
resolve(null);
})
.finally(() => {
Status.showCheckDeviceLoading = false;
});
});
// return new Promise((resolve, reject) => {
// let para = {
// deviceStatus: 1,
// isTid: true,
// sid: cEdit.sid,
// pageNum: checkedPageParams.pageNum,
// pageSize: checkedPageParams.pageSize,
// deviceImei: searchFilter.value.checkedKey
// };
// checkedPageParams.total = 0;
// Status.showCheckDeviceLoading = true;
// getDevice(para)
// .then((res) => {
// console.log('查询已选择设备成功');
// if (res.code === 200) {
// checkedDevices.value = res.rows;
// checkedPageParams.total = res.total;
// resolve(res);
// return;
// }
// resolve(null);
// })
// .catch((ex) => {
// resolve(null);
// })
// .finally(() => {
// Status.showCheckDeviceLoading = false;
// });
// });
}
//关闭选择终端
function CloseCheckDevice() {
Status.showCheckDevice = false;
}
//保存选择的终端
function SaveChecked() {
if (ids.value.length === 0) {
alert('请选择终端');
return;
}
if (ids.value.length > 100) {
alert('最多只能选100条数据');
return;
}
let tids = ids.value
.map((v) => {
return v.tid;
})
.join(',');
let deviceIds = ids.value.map((v) => {
return v.deviceId;
});
let mapAdd = () => {
//先从高德添加数据,成功后再提交后台
return new Promise((resolve, reject) => {
let url = 'https://tsapi.amap.com/v1/track/geofence/terminal/bind';
let method = 'POST';
let data = {
key: mapApiKey,
sid: checkGf.sid,
gfid: checkGf.gfid,
tids: tids
};
request({ url: url, method: method, data: data })
.then((res) => {
if (res && res.errcode === 10000) {
resolve(res);
return;
// alert('操作成功');
//
} else {
alert(res.errmsg);
reject(res);
}
})
.catch((ex) => {
reject(ex);
});
});
};
mapAdd()
.then((res) => {
let json = {
sid: checkGf.sid,
fenceId: checkGf.gfid,
deviceIds: deviceIds
};
api
.addTerminal(json)
.then((result) => {
//高德添加成功了,提交到后台
if (result && result.code === 200) {
alert('操作成功');
} else {
alert('在高德保存成功,系统保存失败,失败数据:' + JSON.stringify(json));
}
})
.finally(() => {
SearchDevice();
});
})
.then((ex) => {});
}
//删除终端
function DropDevice(row) {
let arr = [];
if (row) {
arr = [row];
} else {
arr = checkZd.value;
}
if (arr.length === 0) {
alert('请选择要删除的数据');
return;
}
if (arr.length > 100) {
alert('最多只能选择100条数据');
return;
}
let tids = arr
.map((v) => {
return v.tid;
})
.join(',');
let deviceIds = arr.map((v) => {
return v.deviceId;
});
let mapDrop = () => {
//从高德删除
return new Promise((resolve, reject) => {
let url = 'https://tsapi.amap.com/v1/track/geofence/terminal/unbind';
let data = {
key: mapApiKey,
sid: checkGf.sid,
gfid: checkGf.gfid,
tids: tids
};
request({
url: url,
method: 'POST',
data: data
})
.then((res) => {
if (res && res.errcode === 10000) {
resolve(res);
return;
} else {
alert(res.errmsg);
reject(res);
}
})
.catch((ex) => {
alert(JSON.stringify(ex));
reject(ex);
});
});
};
//先从高德删除,再从系统删除
let execDrop = () => {
mapDrop().then((res) => {
let json = {
sid: checkGf.sid,
fenceId: checkGf.gfid,
deviceIds: deviceIds
};
api
.delTerminal(json)
.then((result) => {
//从系统删除
if (result && result.code === 200) {
alert('操作成功');
return;
}
alert('<div class="red">从高德删除成功,但从系统删除失败了,失败数据:' + JSON.stringify(json) + '</div>');
})
.finally(() => {
SearchDevice();
});
});
};
confirm('<div class="red">删除后不可恢复,您确认删除所选数据吗?</div>', execDrop, null, '提示');
}
const handleSelectionChange = (selection: any[]) => {
ids.value = selection;
};
//查询所有服务
function getServices() {
listService({ pageNum: 1, pageSize: 9999 }).then((res) => {
if (res.code === 200) {
services.value = res.rows;
}
});
}
//显示隐藏高级查询
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);
}
} else {
cEdit.id = null;
cEdit.name = '';
cEdit.description = '';
cEdit.areaType = null;
cEdit.coordinates = [];
cEdit.radius = null;
cEdit.isActive = null;
cEdit.sid = null;
cEdit.gfid = null;
}
getServices();
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,
sid: null,
gfid: null
};
let keys = Object.keys(defCfg);
keys.forEach((key) => {
cEdit[key] = defCfg[key];
});
}
//响应查询
var queryTime = null;
function handleQuery() {
clearTimeout(queryTime);
queryTime = setTimeout(getList, 300);
}
//重置查询条件
function resetQuery() {
let cfg = {
name: '',
areaType: null,
isActive: null,
description: ''
};
let keys = Object.keys(cfg);
keys.forEach((k) => {
queryParams[k] = cfg[k];
});
handleQuery();
}
//--------------------------------------
//修改围栏类型时,清空所有坐标
function areaTypeChange() {
cEdit.coordinates = [];
cEdit.radius = undefined;
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;
}
if (!point && cEdit.areaType == 1 && !cEdit.radius) {
cEdit.radius = 1000;
}
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 updateGeoFence() {
const legalChars = cEdit.name.match(/[\u4e00-\u9fa5a-zA-Z0-9_\-]/g) || [];
let geoFenceName = legalChars.join('').slice(0, 128);
let json = {
key: mapApiKey,
sid: cEdit.sid,
name: geoFenceName,
desc: geoFenceName
};
if (cEdit.gfid && cEdit.id) {
//修改围栏
json.gfid = cEdit.gfid;
}
let url = '';
if (cEdit.areaType == 0) {
//多边形新增/修改
json.points = cEdit.coordinates
.map((v) => {
return v.lng + ',' + v.lat;
})
.join(';');
if (!cEdit.id) {
url = 'https://tsapi.amap.com/v1/track/geofence/add/polygon';
} else {
url = 'https://tsapi.amap.com/v1/track/geofence/update/polygon';
}
} else if (cEdit.areaType == 1) {
//圆形新增/修改
json.center = cEdit.coordinates[0].lng + ',' + cEdit.coordinates[0].lat;
json.radius = parseFloat(cEdit.radius);
if (!cEdit.id) {
url = 'https://tsapi.amap.com/v1/track/geofence/add/circle';
} else {
url = 'https://tsapi.amap.com/v1/track/geofence/update/circle';
}
}
return request({
url: url,
method: 'POST',
data: json
});
}
function dropGeoFence(arr) {
let ids = arr.filter((v) => {
return v.gfid;
});
let defaultSucc = Promise.resolve({ errcode: 10000, msg: '操作成功' });
if (ids.length === 0) {
return defaultSucc;
}
let sids = [];
arr.filter((v) => {
let f = sids.find((sid) => {
return sid === v.sid;
});
if (!f && v.sid) {
sids.push(v.sid);
}
});
let task = (sid, gfids) => {
if (!gfids || !sid) {
return defaultSucc;
}
let json = {
key: mapApiKey,
sid: sid,
gfids: gfids
};
const url = 'https://tsapi.amap.com/v1/track/geofence/delete';
return request({
url: url,
method: 'POST',
data: json
});
};
let promises = [];
for (let i = 0; i < sids.length; i++) {
const serviceId = sids[i];
const gfids = arr
.filter((v) => {
return v.sid === serviceId && v.gfid;
})
.map((v) => {
return v.gfid;
})
.join(',');
if (serviceId && gfids) {
promises.push(task(serviceId, gfids));
}
}
if (promises.length) {
return Promise.allSettled(promises)
.then((res) => {
let succIds = [];
for (let i = 0; i < promises.length; i++) {
if (res[i].status === 'fulfilled') {
let result = res[i].value;
if (result.errcode === 10000) {
succIds = succIds.concat(result.data.gfids);
}
}
}
if (succIds.length > 0) {
return Promise.resolve({ errcode: 10000, data: succIds });
} else {
return Promise.resolve({ errcode: 10000, errmsg: 'UNKNOWN_ERROR' });
}
})
.catch((ex) => {
console.log('出现异常', ex);
});
} else {
return defaultSucc;
}
}
function request(cfg) {
return new Promise((resolve, reject) => {
let json = cfg.data;
let url = cfg.url;
let method = cfg.method;
let formData = [];
if (json) {
let keys = Object.keys(json);
for (let index = 0; index < keys.length; index++) {
let key = keys[index];
let encodedKey = encodeURIComponent(key);
let encodedValue = encodeURIComponent(json[key]);
formData.push(`${encodedKey}=${encodedValue}`);
}
}
let queryString = formData.join('&'); // 拼接成 key1=value1&key2=value2
let xhr = new XMLHttpRequest();
xhr.open(method, url, true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded;charset=UTF-8');
xhr.responseType = 'json';
xhr.onreadystatechange = function () {
// readyState=4 表示请求完成status=200 表示响应成功
if (xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status >= 200 && xhr.status < 300) {
// 成功:返回响应数据
resolve(xhr.response);
} else {
// 失败:返回状态码和错误信息
reject(new Error(`请求失败:状态码 ${xhr.status},信息:${xhr.statusText}`));
}
}
};
// 6. 监听网络错误(如断网、跨域、服务器不可达)
xhr.onerror = function () {
reject(new Error('网络错误:请求无法完成'));
};
// 7. 监听超时(可选,建议设置)
xhr.ontimeout = function () {
reject(new Error('请求超时:超过指定时间未响应'));
};
// 8. 设置超时时间(毫秒)
xhr.timeout = 20000; // 20秒超时
try {
// 9. 发送请求:将 JSON 对象转为字符串
xhr.send(queryString);
} catch (error) {
// 捕获数据序列化错误
reject(error);
}
});
}
//保存数据
function SaveFormData() {
let err = [];
if (!cEdit.sid) {
err.push('请选择服务id');
}
if (!cEdit.name) {
err.push('请填写围栏名称');
}
if (cEdit.areaType === null) {
err.push('请选择围栏类型');
} else {
if (cEdit.areaType == 0 && cEdit.coordinates.length < 3) {
err.push('多边形的围栏至少需要3个点');
}
if (cEdit.areaType == 1 && cEdit.coordinates.length == 0) {
err.push('圆形的围栏至少需要1个点');
}
}
if (err.length) {
alert(err.join('<br/>'));
return;
}
let promise = Promise.resolve({ code: 200, msg: '操作成功' });
Status.fullLoading = true;
let json = Object.assign({}, cEdit);
json.coordinates = JSON.stringify(json.coordinates);
promise = updateGeoFence()
.then((res) => {
if (res.errcode === 10000) {
if (cEdit.id !== null) {
//编辑
return api.updateFence(json);
} else {
//新增
json.gfid = res.data.gfid;
return api.AddFence(json);
}
}
return Promise.resolve({ code: 500, msg: res.errmsg });
})
.catch((ex) => {
return { code: ex.code, msg: ex.message };
});
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;
}
let execDelete = () => {
//先从高德删除围栏,再从后台删除
dropGeoFence(arr).then((res) => {
if (res.errcode === 10000) {
let dropIds = res.data;
if (!dropIds) {
alert('从高德地图删除围栏失败');
return;
}
let ids = [];
arr.filter((v) => {
if (!v.gfid || !v.sid) {
ids.push(v.id);
return true;
}
if (dropIds) {
let f = dropIds.find((gfid) => {
return gfid === v.gfid;
});
if (f) {
ids.push(v.id);
}
} else {
ids.push(v.id);
}
return true;
});
if (!ids.length) {
alert('操作没有成功');
return;
}
ids = ids.join(',');
api.DelFence(ids).then((res) => {
if (res.code == 200) {
handleQuery();
}
alert(res.msg);
});
} else {
alert(res.errmsg);
}
});
};
confirm('删除后不可恢复,您确认?', execDelete, null, '提示');
}
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: calc(100% - 200px);
box-sizing: border-box;
border: 1px solid #ccc;
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);
}
:deep .el-dialog__body,
.el-dialog__body {
position: relative;
}
:deep .el-dialog__body .footerBtn,
.el-dialog__body .footerBtn {
// position: absolute;
// bottom: 0px;
// width: 100%;
// left: 0px;
}
.deviceLayer .gridContent {
width: 100%;
height: calc(100% - 50px);
}
.button-row {
margin-top: 10px;
width:auto;
height: 36px;
box-sizing: border-box;
}
.popLayer {
position: relative;
}
.popLayer .abClose {
position: absolute;
top: 15px;
right: 15px;
z-index: 9;
cursor: pointer;
}
.red,
:deep .red {
color: #ff0000;
}
</style>