1409 lines
38 KiB
Vue
1409 lines
38 KiB
Vue
<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>
|