|
|
@@ -0,0 +1,368 @@
|
|
|
+<template>
|
|
|
+ <wrapper
|
|
|
+ fill
|
|
|
+ margin
|
|
|
+ horizontal
|
|
|
+ >
|
|
|
+ <div class="l-flex__fill l-flex--col c-sibling-item c-device-map">
|
|
|
+ <div class="l-flex__none l-flex--row c-count">
|
|
|
+ <div
|
|
|
+ class="l-flex__none c-count__title u-color--black u-bold has-active u-ellipsis"
|
|
|
+ @click="onChooseDepartment"
|
|
|
+ >
|
|
|
+ <i
|
|
|
+ v-if="loading"
|
|
|
+ class="el-icon-loading"
|
|
|
+ />
|
|
|
+ {{ group.name }}
|
|
|
+ </div>
|
|
|
+ <div class="l-flex__none c-count__item u-color--black u-bold u-text--center">
|
|
|
+ <div>总数</div>
|
|
|
+ <i
|
|
|
+ v-if="monitor.loading"
|
|
|
+ class="el-icon-loading"
|
|
|
+ />
|
|
|
+ <div v-else>{{ monitor.total }}</div>
|
|
|
+ </div>
|
|
|
+ <div class="l-flex__none c-count__item u-color--success dark u-bold u-text--center">
|
|
|
+ <div>● 在线</div>
|
|
|
+ <i
|
|
|
+ v-if="monitor.loading"
|
|
|
+ class="el-icon-loading"
|
|
|
+ />
|
|
|
+ <div v-else>{{ monitor.online }}</div>
|
|
|
+ </div>
|
|
|
+ <div class="l-flex__none c-count__item u-color--error dark u-bold u-text--center">
|
|
|
+ <div>● 离线</div>
|
|
|
+ <i
|
|
|
+ v-if="monitor.loading"
|
|
|
+ class="el-icon-loading"
|
|
|
+ />
|
|
|
+ <div v-else>{{ monitor.offline }}</div>
|
|
|
+ </div>
|
|
|
+ <div class="l-flex__none c-count__item u-color--info u-bold u-text--center">
|
|
|
+ <div>● 未启用</div>
|
|
|
+ <i
|
|
|
+ v-if="monitor.loading"
|
|
|
+ class="el-icon-loading"
|
|
|
+ />
|
|
|
+ <div v-else>{{ monitor.inactive }}</div>
|
|
|
+ </div>
|
|
|
+ <i
|
|
|
+ class="el-icon-refresh o-icon md u-color--blue has-active"
|
|
|
+ @click="onRefresh"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <div
|
|
|
+ ref="map"
|
|
|
+ class="l-flex__fill u-relative"
|
|
|
+ >
|
|
|
+ <i
|
|
|
+ v-if="deviceOptions.loaded"
|
|
|
+ class="o-place el-icon-place has-active"
|
|
|
+ @click="onPlace"
|
|
|
+ />
|
|
|
+ <device
|
|
|
+ v-if="device"
|
|
|
+ :key="device.id"
|
|
|
+ class="c-device-map__device"
|
|
|
+ :device="device"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <!-- <div
|
|
|
+ ref="devicelist"
|
|
|
+ v-loading="!deviceOptions.loaded"
|
|
|
+ class="l-flex__none l-flex--col c-sibling-item u-width--lg u-overflow-y--auto"
|
|
|
+ >
|
|
|
+ <device
|
|
|
+ v-for="item in deviceOptions.list"
|
|
|
+ :key="item.id"
|
|
|
+ class="c-sibling-item--v"
|
|
|
+ :device="item"
|
|
|
+ />
|
|
|
+ </div> -->
|
|
|
+ <department-drawer
|
|
|
+ ref="departmentDrawer"
|
|
|
+ @change="onGroupChanged"
|
|
|
+ @loaded="onGroupLoaded"
|
|
|
+ />
|
|
|
+ </wrapper>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import AMapLoader from '@amap/amap-jsapi-loader'
|
|
|
+import {
|
|
|
+ subscribe,
|
|
|
+ unsubscribe
|
|
|
+} from '@/utils/mqtt'
|
|
|
+import {
|
|
|
+ getDevicesByQuery,
|
|
|
+ getDeviceStatisticsByPath
|
|
|
+} from '@/api/device'
|
|
|
+import Device from '../components/Device.vue'
|
|
|
+
|
|
|
+const onlineIcon = require('./assets/icon_position1.svg')
|
|
|
+const offlineIcon = require('./assets/icon_position2.svg')
|
|
|
+
|
|
|
+export default {
|
|
|
+ name: 'DeviceMap',
|
|
|
+ components: {
|
|
|
+ Device
|
|
|
+ },
|
|
|
+ data () {
|
|
|
+ return {
|
|
|
+ monitor: { loading: true },
|
|
|
+ deviceOptions: {
|
|
|
+ list: [],
|
|
|
+ loaded: false
|
|
|
+ },
|
|
|
+ loading: true,
|
|
|
+ group: {},
|
|
|
+ device: null
|
|
|
+ }
|
|
|
+ },
|
|
|
+ computed: {
|
|
|
+ mapDevices () {
|
|
|
+ return this.deviceOptions.list.filter(i => i.longitude && i.latitude)
|
|
|
+ }
|
|
|
+ },
|
|
|
+ created () {
|
|
|
+ this.$timer = -1
|
|
|
+ subscribe([
|
|
|
+ '+/+/online',
|
|
|
+ '+/+/offline',
|
|
|
+ '+/+/calendar/update'
|
|
|
+ ], this.onMessage)
|
|
|
+ },
|
|
|
+ beforeDestroy () {
|
|
|
+ clearTimeout(this.$timer)
|
|
|
+ this.monitor = { loading: true }
|
|
|
+ unsubscribe([
|
|
|
+ '+/+/online',
|
|
|
+ '+/+/offline',
|
|
|
+ '+/+/calendar/update'
|
|
|
+ ], this.onMessage)
|
|
|
+ this.map?.destroy()
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ onMessage (topic) {
|
|
|
+ if (!this.deviceOptions.loaded) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ const result = /^\d+\/(\d+)\/(online|offline)$/.exec(topic)
|
|
|
+ if (!result) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ const deviceId = result[1]
|
|
|
+ const status = result[2]
|
|
|
+ const device = this.deviceOptions.list.find(({ id }) => id === deviceId)
|
|
|
+ if (device) {
|
|
|
+ const onlineStatus = device.onlineStatus === 1 ? 'online' : 'offline'
|
|
|
+ if (status === onlineStatus) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ this.refreshDevices()
|
|
|
+ }
|
|
|
+ },
|
|
|
+ onGroupLoaded () {
|
|
|
+ this.loading = false
|
|
|
+ },
|
|
|
+ onGroupChanged ({ path, name }) {
|
|
|
+ if (!this.group || this.group.path !== path) {
|
|
|
+ this.group = { path, name }
|
|
|
+ this.refreshDevices(true)
|
|
|
+ }
|
|
|
+ },
|
|
|
+ onChooseDepartment () {
|
|
|
+ this.$refs.departmentDrawer.show().then(visible => {
|
|
|
+ this.loading = !visible
|
|
|
+ })
|
|
|
+ },
|
|
|
+ onRefresh () {
|
|
|
+ this.refreshDevices()
|
|
|
+ },
|
|
|
+ refreshDevices (force) {
|
|
|
+ if (!force && this.monitor.loading) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ const monitor = {
|
|
|
+ loading: true,
|
|
|
+ total: '-',
|
|
|
+ online: '-',
|
|
|
+ offline: '-',
|
|
|
+ inactive: '-'
|
|
|
+ }
|
|
|
+ this.device = null
|
|
|
+ this.map?.destroy()
|
|
|
+ this.map = null
|
|
|
+ this.monitor = monitor
|
|
|
+ clearTimeout(this.$timer)
|
|
|
+ this.deviceOptions = { loaded: false }
|
|
|
+ getDeviceStatisticsByPath(this.group.path).then(({ data }) => {
|
|
|
+ const { deactivatedTotal, notConnectedTotal, offLineTotal, onLineTotal, total } = data
|
|
|
+ monitor.total = total
|
|
|
+ monitor.online = onLineTotal
|
|
|
+ monitor.offline = offLineTotal + notConnectedTotal
|
|
|
+ monitor.inactive = deactivatedTotal
|
|
|
+ }).finally(() => {
|
|
|
+ monitor.loading = false
|
|
|
+ if (!this.monitor.loading) {
|
|
|
+ this.getDevices(this.monitor.total - this.monitor.inactive)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ },
|
|
|
+ sort (a, b) {
|
|
|
+ if (a.onlineStatus === b.onlineStatus) {
|
|
|
+ return a.createTime <= b.createTime ? 1 : -1
|
|
|
+ }
|
|
|
+ return a.onlineStatus === 1 ? -1 : 1
|
|
|
+ },
|
|
|
+ getDevices (total) {
|
|
|
+ if (!total || total === '-') {
|
|
|
+ this.deviceOptions = { list: [], loaded: true }
|
|
|
+ return
|
|
|
+ }
|
|
|
+ const options = { list: [], loaded: false }
|
|
|
+ this.deviceOptions = options
|
|
|
+ getDevicesByQuery({
|
|
|
+ pageNum: 1,
|
|
|
+ pageSize: total,
|
|
|
+ activate: 1,
|
|
|
+ org: this.group.path
|
|
|
+ }, { custom: true }).then(
|
|
|
+ ({ data }) => {
|
|
|
+ options.list = data.sort(this.sort)
|
|
|
+ options.loaded = true
|
|
|
+ this.initMap()
|
|
|
+ },
|
|
|
+ ({ isCancel }) => {
|
|
|
+ if (!isCancel && !this.monitor.loading) {
|
|
|
+ this.$timer = setTimeout(total => {
|
|
|
+ this.getDevices(total)
|
|
|
+ }, 2000, total)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ )
|
|
|
+ },
|
|
|
+ onDeviceChange (device) {
|
|
|
+ if (!device) {
|
|
|
+ this.map.setFitView()
|
|
|
+ return
|
|
|
+ }
|
|
|
+ this.activeDevice(device.id, [device.marker])
|
|
|
+ },
|
|
|
+ initMap () {
|
|
|
+ AMapLoader.load({
|
|
|
+ key: process.env.VUE_APP_GAODE_MAP_KEY,
|
|
|
+ version: '2.0',
|
|
|
+ plugins: ['']
|
|
|
+ }).then(AMap => {
|
|
|
+ this.map = new AMap.Map(this.$refs.map)
|
|
|
+ const markerList = []
|
|
|
+ for (const device of this.mapDevices) {
|
|
|
+ const marker = new AMap.Marker({
|
|
|
+ position: [+device.longitude, +device.latitude],
|
|
|
+ icon: new AMap.Icon({
|
|
|
+ image: device.onlineStatus === 1 ? onlineIcon : offlineIcon,
|
|
|
+ size: new AMap.Size(32, 32),
|
|
|
+ imageSize: new AMap.Size(32, 32)
|
|
|
+ }),
|
|
|
+ label: {
|
|
|
+ content: `<div class='o-online-status--${device.onlineStatus === 1 ? 'online' : 'offline'}'>${device.name}</div>`,
|
|
|
+ direction: 'top',
|
|
|
+ offset: new AMap.Pixel(0, -10) // 设置文本标注偏移量
|
|
|
+ },
|
|
|
+ extData: {
|
|
|
+ id: device.id
|
|
|
+ }
|
|
|
+ })
|
|
|
+ marker.on('click', e => {
|
|
|
+ this.activeDevice(e.target.getExtData().id, e.target)
|
|
|
+ })
|
|
|
+ device.marker = marker
|
|
|
+ markerList.unshift(marker)
|
|
|
+ }
|
|
|
+ this.map.add(markerList)
|
|
|
+ this.map.setFitView()
|
|
|
+ this.map.on('click', () => {
|
|
|
+ this.device = null
|
|
|
+ })
|
|
|
+ })
|
|
|
+ },
|
|
|
+ onPlace () {
|
|
|
+ this.map.setFitView()
|
|
|
+ },
|
|
|
+ activeDevice (id, marker) {
|
|
|
+ this.map.setFitView(marker)
|
|
|
+ this.device = this.mapDevices.find(device => device.id === id)
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</script>
|
|
|
+<style lang="scss" scoped>
|
|
|
+@mixin markLabel {
|
|
|
+ color: #ffffff;
|
|
|
+ border: none;
|
|
|
+ padding: 5px 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.c-device-map {
|
|
|
+ border-radius: $radius $radius 0 0;
|
|
|
+ background-color: #fff;
|
|
|
+
|
|
|
+ &__device {
|
|
|
+ position: absolute;
|
|
|
+ top: $spacing;
|
|
|
+ left: $spacing;
|
|
|
+ width: $width--lg;
|
|
|
+ z-index: 9;
|
|
|
+ }
|
|
|
+
|
|
|
+ ::v-deep {
|
|
|
+ .o-online-status {
|
|
|
+ &--online {
|
|
|
+ @include markLabel();
|
|
|
+ background-color: #333333;
|
|
|
+ }
|
|
|
+ &--offline {
|
|
|
+ @include markLabel();
|
|
|
+ background-color: #f90c0c;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .amap-marker-label {
|
|
|
+ border: none;
|
|
|
+ padding: 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.c-count {
|
|
|
+ justify-content: space-between;
|
|
|
+ padding: $spacing--xs $spacing;
|
|
|
+
|
|
|
+ &__title {
|
|
|
+ width: 200px;
|
|
|
+ }
|
|
|
+
|
|
|
+ &__item > div:first-child {
|
|
|
+ margin-bottom: 10px;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.o-place {
|
|
|
+ display: inline-flex;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+ position: absolute;
|
|
|
+ right: $spacing;
|
|
|
+ bottom: $spacing;
|
|
|
+ width: 48px;
|
|
|
+ height: 48px;
|
|
|
+ font-size: 24px;
|
|
|
+ border-radius: 50%;
|
|
|
+ background-color: #fff;
|
|
|
+ z-index: 9;
|
|
|
+}
|
|
|
+</style>
|