You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1012 lines
27 KiB
Vue

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<div class="container">
<div class="centerImg" v-if="!isMap"
:style="`background-image: url(${picUrl ? picUrl : containerPic})`"></div>
<div class="title">监控单元统计</div>
<Chart ref="chart1" class="chart1"></Chart>
<div class="chart1Right">
<p v-for="(i,k) in chart1Data.slice(0,4)" :key="k" :style="'margin:0.6vw;color:'+ chart1Color[k]">{{ i.name }}</p>
</div>
<div class="chart1Table">
<div style="background-color: #094170">
<div class="scrollTable" style="font-weight: bold;">
名称
</div>
<div class="scrollTable" style="font-weight: bold;">
监控数量
</div>
<div class="scrollTable" style="font-weight: bold;">
报警数量
</div>
</div>
<vue-seamless-scroll
:class-option="chart1TableOption"
:data="chart1Data"
class="case-item"
style="height: 84%;overflow: hidden;"
>
<div
v-for="(item, index) in chart1Data"
:key="index"
>
<div :style='"background-color:" + ((index % 2 === 0)? "#053460":"#032d57") '>
<div
class="scrollTable">
{{ item.name }}
</div>
<div
class="scrollTable">
{{ item.value }}
</div>
<div
class="scrollTable">
{{ item.value2 }}
</div>
</div>
</div>
</vue-seamless-scroll>
</div>
<Chart ref="chart2" class="chart2"></Chart>
<div class="chart2Table">
<div style="background-color: #094170">
<div class="scrollTable" style="font-weight: bold;width: 50%">
名称
</div>
<div class="scrollTable" style="font-weight: bold;width: 50%">
数量
</div>
</div>
<vue-seamless-scroll
:class-option="chart1TableOption"
:data="chart2Data"
class="case-item"
style="height: 84%;overflow: hidden;"
>
<div
v-for="(item, index) in chart2Data"
:key="index"
>
<div :style='"background-color:" + ((index % 2 === 0)? "#053460":"#032d57") '>
<div class="scrollTable" style="width: 50%">
{{ item.name }}
</div>
<div class="scrollTable" style="width: 50%">
{{ item.value }}
</div>
</div>
</div>
</vue-seamless-scroll>
</div>
<div>
<div class="centerInfo">传感器数量</div>
<div class="centerInfo" style="font-weight:bold;top:19.3%;left: 42.5%;font-size: 2vw;">{{
centerNum.sum
}}
</div>
<div class="centerInfo" style="left: 56%">监控单元数量</div>
<div class="centerInfo" style="font-weight:bold;top:19.3%;left: 66%;font-size: 2vw;">{{ centerNum.subSum }}</div>
</div>
<div class="inTransit">在运: <span style="color: #00f6ff">{{ inTransitNum }}</span></div>
<Chart ref="chart3" class="chart3"></Chart>
<div class="table1">
<div style="background-color: #094170">
<div class="scrollTable" style="font-weight: bold;width: 25%">
告警编号
</div>
<div class="scrollTable" style="font-weight: bold;width: 25%">
告警类型
</div>
<div class="scrollTable" style="font-weight: bold;width: 25%">
告警位置
</div>
<div class="scrollTable" style="font-weight: bold;width: 25%">
操作
</div>
</div>
<vue-seamless-scroll
:class-option="{...chart1TableOption,limitMoveNum:10}"
:data="table1Data"
class="case-item"
style="height: calc(100% - 33px);overflow: hidden;"
>
<div
v-for="(item, index) in table1Data"
:key="index"
>
<div :style='"background-color:" + ((index % 2 === 0)? "#053460":"#032d57") '>
<div class="scrollTable" style="width: 25%">
{{ item.no }}
</div>
<div class="scrollTable" style="width: 25%">
{{ item.type }}
</div>
<div class="scrollTable" style="width: 25%">
{{ item.location }}
</div>
<div class="scrollTable" style="width: 25%">
<el-button v-if="item.status === '0'" size="mini" type="primary" @click="dispose(item)">处理</el-button>
<span v-else>已处理</span>
</div>
</div>
</div>
</vue-seamless-scroll>
</div>
<el-button type="primary" class="exTable"
@click="exTable">导出告警信息
</el-button>
<div v-if="isMap">
<Chart ref="mapChart" class="mapChart"></Chart>
<el-button type="primary" style="position:absolute;bottom: 4%;left: 50%;transform: translateX(-50%)"
@click="toRealMap">打开地图
</el-button>
</div>
<el-dialog :visible.sync="realMap" width="80%">
<el-form :inline="true" :model="formInline" class="demo-form-inline">
<el-form-item label="名称">
<el-input v-model="formInline.name" placeholder="名称"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="searchMap">查询</el-button>
</el-form-item>
</el-form>
<div id="map" class="map"></div>
</el-dialog>
<el-dialog
:visible.sync="isDispose"
width="30%">
<span>备注</span>
<el-input
style="margin-top: 12px;margin-bottom:12px"
type="textarea"
:rows="2"
placeholder="请输入内容"
v-model="textarea">
</el-input>
<span slot="footer" class="dialog-footer">
<el-button @click="isDispose = false">取 消</el-button>
<el-button type="primary" @click="disposeThis">处理当前</el-button>
<el-button type="primary" @click="disposeAll">处理所有</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import containerPic from "@/assets/board/index/container.png";
import Chart from "@/components/Charts/Chart";
import vueSeamlessScroll from "vue-seamless-scroll";
import * as echarts from 'echarts';
import {
monitorUnitPercentage,
monitorPercentage,
allNums,
getAlarmInfos,
getDeviceOperations,
handleAlarmInfo,
getDeviceByAreaId,
getTenantData,
selectMonitorElectronic,
selectDeviceByName,
exTableData
} from '@/api/board/index'
import ChinaMapData from '@/utils/ChinaMapData.json'
import gsByMap from '@/utils/map/gs_by.json'
import axios from "axios";
let map = null
let markers = []
let markerIds = []
let polygons = []
let circles = []
const vw = (document.documentElement.clientWidth || document.body.clientWidth) / 100
export default {
components: {
vueSeamlessScroll,
Chart,
},
data() {
return {
containerPic,
formInline:{},
textarea: '',
disposeNo: null,
isDispose: false,
realMap: false,
centerGeo: [],
picUrl: null,
code: 620000,
cityJson: null,
isMap: true,
centerNum: {},
chart1Option: {
tooltip: {
valueFormatter: (e) => {
return e + '%'
},
},
angleAxis: {
max: 100,
clockwise: true,
show: false,
},
radiusAxis: {
type: "category",
show: true,
axisLabel: {
show: false,
},
axisLine: {
show: false,
},
axisTick: {
show: false,
},
},
polar: {
center: ["30%", "50%"],
radius: "100%", //图形大小
},
},
chart1Color: ['#2ff', '#f22', '#f2f', '#1f1'],
chart1Data: [],
chart1TableOption: {
step: 0.5, // 数值越大速度滚动越快
limitMoveNum: 3, // 开始无缝滚动的数据量 this.dataList.length
hoverStop: true, // 是否开启鼠标悬停stop
direction: 1, // 0向下 1向上 2向左 3向右
openWatch: true, // 开启数据实时监控刷新dom
singleHeight: 0, // 单步运动停止的高度(默认值0是无缝不停止的滚动) direction => 0/1
singleWidth: 0, // 单步运动停止的宽度(默认值0是无缝不停止的滚动) direction => 2/3
waitTime: 0,
},
chart2Data: [],
chart2Option: {
color: ["#22acfd", "#22fe97", "#f9e728", "#ff922b", "#ed1814"],
legend: {
type: 'scroll',
orient: "vertical",
x: "right",
y: "center",
width: '100px',
textStyle: {
color: '#fff'
}
},
tooltip: {
trigger: "item",
formatter: "{b} : ({d}%)",
},
series: [
{
type: "pie",
radius: ["45%", "76%"],
center: ["35%", "50%"],
encode: {
itemName: "group",
value: "占比",
},
labelLine: {
show: false,
},
label: {
show: false,
},
itemStyle: {
opacity: '0.8'
}
},
{
type: "pie",
radius: ["75%", "90%"],
center: ["35%", "50%"],
encode: {
itemName: "group",
value: "占比",
},
labelLine: {
show: false,
},
label: {
show: false,
},
},
],
},
inTransitNum: 0,
chart3Option: {
tooltip: {
trigger: "axis",
},
grid: {
top: "middle",
left: "3%",
right: "4%",
bottom: "3%",
height: "80%",
containLabel: true,
},
yAxis: {
type: "value",
splitLine: {
lineStyle: {
type: "dashed",
color: "#ccc3",
},
},
axisLine: {
show: false,
lineStyle: {
color: "#ccc",
},
},
nameTextStyle: {
color: "#999",
},
splitArea: {
show: false,
},
},
},
table1Data: []
}
},
async mounted() {
getTenantData(this.$store.getters.tenantId).then(e => {
// this.isMap = e.data.tenantField === '2';
this.picUrl = e.data.tenantBoardPic
this.code = e.data.tenantMapCode || 620000
})
this.setChart1()
this.setChart2()
this.setAllNums()
this.setTable3()
this.setDeviceOperations()
let data = await axios.get(`https://geo.datav.aliyun.com/areas_v3/bound/${this.code}_full.json`)
let mapData = this.code === 100000 ? ChinaMapData : data.data
await this.getMap(mapData)
this.chartClick()
},
methods: {
async setChart1() {
const {rows: data} = await monitorPercentage()
let data1 = data.slice(0, 4)
data1.reverse()
let option1 = {
...this.chart1Option,
series: [
{
type: "bar",
data: data1.map((e, i) => {
return {
value: parseFloat(e.percentage),
name: e.sceneName,
itemStyle: {
color: this.chart1Color[i]
}
}
}),
showBackground: true,
coordinateSystem: "polar",
roundCap: true,
barWidth: 8,
},
]
}
this.$refs.chart1.setData(option1)
this.chart1Data = data.map(e => {
return {
value: e.sum || 0,
value2: e.err || 0,
name: e.sceneName || '',
}
})
},
async setChart2() {
const {rows: data} = await monitorUnitPercentage()
let chart2Num = eval(data.map(e => e.sum).join("+"));
let option2 = {
...this.chart2Option,
dataset: {
source: [
["group", "占比"],
...data.slice(0, 7).map(e => [e.deviceModeName, parseFloat(e.percentage)])
],
}
}
this.$refs.chart2.setData(option2)
this.chart2Data = data.map(e => {
return {
value: e.sum,
name: e.deviceModeName,
}
})
},
async setAllNums() {
const data = await allNums()
this.centerNum = data
},
async setTable3() {
const {rows: data} = await getAlarmInfos()
this.table1Data = data.map((e, i) => {
return {
no: e.alarmInfoId,
type: e.alarmTypeName,
location: e.monitorUnitName,
status: e.handleStatus
}
})
},
async setDeviceOperations() {
let {data: data} = await getDeviceOperations()
let onlineDevicesTrend = data["onlineDevicesTrend"];
let sortArr = Object.keys(onlineDevicesTrend)
sortArr = sortArr.sort((a, b) => {
return new Date(a).getTime() - new Date(b).getTime()
})
console.log(sortArr)
let x = sortArr
let y = sortArr.map(e => {
return onlineDevicesTrend[e]
})
// this.inTransitNum = y.reduce((a, b) => {
// return a + b
// }, 0)
this.inTransitNum = data["onlineDevicesCount"];
let option3 = {
...this.chart3Option,
xAxis: {
boundaryGap: false,
type: "category",
data: x,
splitLine: {
show: true,
lineStyle: {
type: "dashed",
color: "#ccc3",
},
},
axisLine: {
lineStyle: {
color: "#999",
},
},
},
series: [
{
name: "",
type: "line",
data: y,
color: "#00dae0",
areaStyle: {
normal: {
color: new echarts.graphic.LinearGradient(
0,
0,
0,
1,
[
{
offset: 0,
color: "#00dae033",
},
{
offset: 1,
color: "rgba(0,202,149,0)",
},
],
false
),
shadowColor: "rgba(0,202,149, 0.9)",
shadowBlur: 20,
},
},
},
],
}
this.$refs.chart3.setData(option3)
},
dispose(e) {
this.textarea = ''
this.isDispose = true
this.disposeNo = e.no
},
disposeThis() {
handleAlarmInfo({
alarmInfoId: this.disposeNo,
alarmInfoField: this.textarea,
ifDisposalAll: 0,
}).then(e => {
if (e.code === 200) {
this.$message({
type: 'success',
message: '已处理!'
});
this.setTable3()
} else {
this.$message({
type: 'info',
message: '网络错误'
});
}
this.isDispose = false
})
},
disposeAll() {
handleAlarmInfo({
alarmInfoId: this.disposeNo,
alarmInfoField: this.textarea,
ifDisposalAll: 1,
}).then(e => {
if (e.code === 200) {
this.$message({
type: 'success',
message: '已处理!'
});
this.setTable3()
} else {
this.$message({
type: 'info',
message: '网络错误'
});
}
this.isDispose = false
})
},
async getMap(geoJson) {
this.cityJson = geoJson.features
echarts.registerMap('map', geoJson);
this.centerGeo = geoJson.features[0].properties.center || geoJson.features[0].properties.geo_wkt.split('(')[1].split(')')[0].split(' ').map(e => parseFloat(e))
const random = (val = 1) => {
return Math.ceil(Math.random() * val)
}
let Point = [
{value: [120.3, 36.0], index: random(5), type: random(5), state: random(5)},
{value: [104.065735, 30.659462], index: random(5), type: random(5), state: random(5)},
{value: [123.1238, 42.1216], index: random(5), type: random(5), state: random(5)},
{value: [114.4995, 38.1006], index: random(5), type: random(5), state: random(5)},
{value: [109.1162, 34.2004], index: random(5), type: random(5), state: random(5)},
{value: [106.3586, 38.1775], index: random(5), type: random(5), state: random(5)},
{value: [101.4038, 36.8207], index: random(5), type: random(5), state: random(5)},
{value: [113.0823, 28.2568], index: random(5), type: random(5), state: random(5)},
{value: [102.9199, 25.46639], index: random(5), type: random(5), state: random(5)},
]
Point = []
let option = {
grid: {
top: '0%',
left: '0%',
right: '0%',
bottom: '0%',
},
geo: {
map: "map",
show: false,
aspectScale: 0.75, //长宽比
zoom: 1.1,
label: {
show: true
},
roam: true,
layoutCenter: ["50%", "50%"], // position位置
layoutSize: 25 * vw,
itemStyle: {
normal: {
areaColor: {
type: "radial",
x: 0.5,
y: 0.5,
r: 0.8,
colorStops: [
{
offset: 0,
color: "#013B8F", // 0% 处的颜色
},
{
offset: 1,
color: "#013B8F", // 100% 处的颜色
},
],
globalCoord: true, // 缺省为 false
},
shadowColor: "#013B8F",
shadowOffsetX: 0,
shadowOffsetY: 8,
},
emphasis: {
areaColor: "#2AB8FF",
borderWidth: 0,
color: "green",
label: {
show: false,
},
},
},
},
series: [
{
type: "map",
roam: 'scale',
label: {
normal: {
show: true,
textStyle: {
color: "#1DE9B6",
},
},
},
itemStyle: {
normal: {
borderColor: "rgb(147, 235, 248,0.5)",
borderWidth: 1,
areaColor: {
type: "radial",
x: 0.5,
y: 0.5,
r: 0.8,
colorStops: [
{
offset: 0,
color: "#013B8F", // 0% 处的颜色
},
{
offset: 1,
color: "#013B8F", // 100% 处的颜色
},
],
globalCoord: true, // 缺省为 false
},
},
},
zoom: 1.1,
map: 'map', //使用
aspectScale: 0.75, //长宽比
},
{
type: "effectScatter",
coordinateSystem: "geo",
showEffectOn: 'emphasis',
zlevel: 1,
rippleEffect: {
period: 15,
scale: 1,
brushType: "fill",
},
hoverAnimation: true,
itemStyle: {
normal: {
color: "#1DE9B6",
shadowBlur: 10,
shadowColor: "#333",
},
},
symbolSize: [0.5 * vw, 0.5 * vw],
data: Point,
label: {
padding: [-16, 0, 0, 5],
show: true,
position: 'bottom',
formatter: function (val) {
return [`{a|}{b|'点位${val.data.index}}`]
},
rich: {
a: {
width: 0.34 * vw,
height: 0.34 * vw,
borderColor: '#0FF32E',
borderWidth: 0.34 * vw,
borderRadius: 0.17 * vw,
shadowBlur: 20,
shadowColor: "#0FF32E",
},
b: {
padding: [0, 0, 0, 5],
color: '#0FF32E',
height: 40,
fontSize: 0.75 * vw,
},
}
}
},
],
};
this.$refs.mapChart.setData(option);
},
chartClick() {
this.$refs.mapChart.chart.on('click', async (params) => {
this.code = this.cityJson[params.dataIndex].properties.adcode || this.code || gsByMap.features[params.dataIndex].properties.unique_id
let mapData
if (this.code !== 620400 && this.code.toString().startsWith('6204')) {
mapData = {features: gsByMap.features.filter(e => e.properties.pid === this.code.toString())}
} else {
let data = await axios.get(`https://geo.datav.aliyun.com/areas_v3/bound/${this.code}_full.json`)
mapData = this.cityJson[params.dataIndex].properties.adcode === 100000 ? ChinaMapData : data.data
}
this.getMap(mapData)
})
this.$refs.mapChart.$el.addEventListener('dblclick', () => {
this.chartDblClick(this.code)
});
},
async chartDblClick(val) {
let a = val.toString().substr(0, 2)
let b = val.toString().substr(2, 2)
let c = val.toString().substr(4, 2)
let d = val.toString().substr(6, 9)
d = null
if (d && d !== '000') {
this.code = Number(a + b + c)
} else if (c !== '00') {
this.code = Number(a + b + '00')
} else if (b !== '00') {
this.code = Number(a + '0000')
} else if (a !== '10') {
this.code = 100000
}
let mapData
if (this.code !== 620400 && this.code.toString().startsWith('6204')) {
mapData = gsByMap
} else {
let data = await axios.get(`https://geo.datav.aliyun.com/areas_v3/bound/${this.code}_full.json`)
mapData = this.code === 100000 ? ChinaMapData : data.data
}
this.getMap(mapData)
},
async toRealMap() {
this.realMap = true
markerIds = []
this.$nextTick(async () => {
this.createMap()
const data = await getDeviceByAreaId(this.$store.getters.tenantId)
data.data.map(e => e.devicesList).flat(1).forEach(e => {
this.setMarker(e)
})
})
},
setMarker(e) {
let marker = new AMap.Marker({
position: [e.longitude, e.latitude],
title: `信息\n经度${e.longitude}\n纬度${e.latitude}\n名称${e.deviceName}\n`,
offset: new AMap.Pixel(-15, -30),
content: `<div>
<svg t="1718261114618" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4305" style="width: 30px;height: 30px;">
<path d="M512 64.5c-180.5 0-326.9 146.4-326.9 326.9 0 63.3 18 122.3 49.2 172.4 1.1 2 2.1 4 3.2 5.9 34.9 58.2 113.9 128.8 165.5 194.5 66.3 84.4 93.2 158 93.2 158 1.6 1.4 13.1 35.6 15.1 35.8 2.5 0.2 12.6-34 14.5-35.8 0 0 22-69.2 83.7-146.6 56-70.1 142.8-145.7 177-206 0.9-1.6 1.8-3.3 2.6-5 31.5-50.2 49.7-109.6 49.7-173.3 0.1-180.5-146.3-326.8-326.8-326.8z" fill="#3D93FD" p-id="4306">
</path>
</svg>
</div>`
});
marker.on('click', async () => {
if (markerIds.includes(e.deviceId)) {
return
} else {
markerIds.push(e.deviceId)
}
const {data} = await selectMonitorElectronic(e.deviceId)
data.map(e => e.hwFenceAreaList).flat(1).forEach(e => {
if (e.areaShapeFlag === '1') {
this.setPolygon(e.areaRange.split('_').map(e => e.split(',').map(v => parseFloat(v))))
}
if (e.areaShapeFlag === '2') {
let arr = e.areaRange.split(',')
this.setCircle([arr[0], arr[1]], arr[2])
}
})
})
marker.on('dblclick', () => {
this.$router.push({path: '/board/equipment', query: {monitorUnitId: e.monitorUnitId}})
});
map.add(marker);
markers.push(marker)
},
setPolygon(e, val) {
// let e = position.map(val => {
// return [val.longitude, val.latitude]
// })
let thisPolygon = new AMap.Polygon({
path: e,
fillColor: '#1791fc',
// fillColor: val > 0 ? '#ff0000' : '#1791fc',
});
console.log(thisPolygon)
map.add(thisPolygon)
map.setFitView(thisPolygon)
polygons.push(thisPolygon)
},
setCircle(center, radius, e) {
let circle = new AMap.Circle({
center,
radius,
borderWeight: 3,
strokeColor: "#FF33FF",
// strokeColor: e ? '#ff0000' : "#FF33FF",
strokeWeight: 6,
strokeOpacity: 0.2,
fillOpacity: 0.4,
strokeDasharray: [10, 10],
fillColor: '#1791fc',
// fillColor: e > 0 ? '#ff0000' : '#1791fc',
})
map.add(circle);
map.setFitView(circle)
circles.push(circle)
},
createMap() {
map = new AMap.Map('map', {
zoom: 11,
center: this.centerGeo,
});
},
async searchMap(){
const {data} = await selectDeviceByName(this.formInline)
map.remove(markers)
map.remove(polygons)
map.remove(circles)
console.log(data)
data.forEach(e=>{
this.setMarker(e)
})
},
exTable(){
exTableData({}, '告警信息')
}
}
}
;
</script>
<style scoped>
.title {
position: absolute;
top: 12%;
left: 13.6%;
font-size: 1vw;
color: #00f8ff;
transform: translateX(-50%);
}
.container {
background-image: url("~@/assets/board/index/bg.jpg");
background-repeat: no-repeat;
background-size: 100% 100%;
width: 100%;
height: calc(100vh);
position: relative;
}
.centerImg {
background-repeat: no-repeat;
background-size: 100% 100%;
position: absolute;
width: 45vw;
height: 20vw;
top: 60%;
left: 50%;
transform: translate(-50%, -50%);
}
.scrollTable {
color: rgb(185, 186, 192);
margin: auto 0px;
padding: 4px 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
text-align: center;
display: inline-block;
width: 33%;
}
.chart1 {
position: absolute;
width: 20%;
height: 21%;
top: 16%;
left: 3%;
}
.chart1Right {
background-image: url("~@/assets/board/common/bg1.png");
background-repeat: no-repeat;
background-size: 100% 100%;
padding: 0 12px;
position: absolute;
top: 17%;
left: 15%;
}
.chart1Table {
position: absolute;
top: 38%;
left: 2.8%;
width: 21%;
height: 14%;
overflow: hidden;
}
.chart2 {
position: absolute;
width: 20%;
height: 21%;
top: 58.5%;
left: 3%;
}
.chart2Table {
position: absolute;
top: 81%;
left: 2.8%;
width: 21%;
height: 15%;
overflow: hidden;
}
.centerInfo {
position: absolute;
width: 7%;
top: 17.5%;
left: 32.7%;
font-size: 1.4vw;
text-align: center;
transform: translateX(-50%);
color: #01c7fd;
letter-spacing: 4px;
}
.inTransit {
position: absolute;
top: 16%;
left: 87%;
transform: translateX(-50%);
font-size: 1.2vw;
color: rgb(30, 158, 223);
}
.chart3 {
position: absolute;
width: 21%;
height: 22%;
top: 21.5%;
left: 76.1%;
}
.table1 {
position: absolute;
width: 21%;
height: 50%;
top: 45.5%;
left: 76.3%;
}
.mapChart {
position: absolute;
width: 50%;
height: 60%;
top: 30%;
left: 25%;
}
.map {
width: 100%;
height: 80vh;
}
.exTable{
position: absolute;
bottom: 0%;
left: 87%;
transform: translateX(-50%);
}
</style>