| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444 |
- <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 class="l-flex__fill l-flex u-relative">
- <div
- ref="map"
- class="l-flex__fill"
- />
- <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"
- :status="device.status"
- always
- />
- </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 { ScreenshotCache } from '@/utils/cache'
- import {
- GET_POWER_STATUS,
- getPowerStatusByMessage
- } from '@/utils/adapter/nova'
- import {
- getDevicesByQuery,
- getDeviceStatisticsByPath,
- getStatusReport
- } 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',
- '+/+/multifunctionCard/invoke/reply'
- ], this.onMessage)
- },
- beforeDestroy () {
- ScreenshotCache.clear()
- clearTimeout(this.$timer)
- this.monitor = { loading: true }
- unsubscribe([
- '+/+/online',
- '+/+/offline',
- '+/+/calendar/update',
- '+/+/multifunctionCard/invoke/reply'
- ], this.onMessage)
- this.map?.destroy()
- },
- methods: {
- getStatusReport (options) {
- if (!options.power.length) {
- return
- }
- getStatusReport(options.power).then(
- ({ data }) => {
- const powerMap = {}
- data.forEach(item => {
- powerMap[item.deviceId] = item.switchStatus
- })
- options.power.forEach(id => {
- if (options.map[id].status === -2) {
- options.map[id].status = powerMap[id] ?? -1
- }
- })
- },
- ({ isCancel }) => {
- if (!isCancel && !this.monitor.loading) {
- setTimeout(() => {
- this.getStatusReport(options)
- }, 1000)
- }
- }
- )
- },
- onMessage (topic, message) {
- if (!this.deviceOptions.loaded) {
- return
- }
- const result = /^\d+\/(\d+)\/(online|offline)|calendar\/update|multifunctionCard\/invoke\/reply$/.exec(topic)
- if (!result) {
- return
- }
- const deviceId = result[1]
- const status = result[2]
- const device = this.deviceOptions.map?.[deviceId]
- if (device) {
- switch (status) {
- case 'calendar/update':
- device.flag = -Date.now()
- break
- case 'multifunctionCard/invoke/reply':
- message = message && JSON.parse(message)
- switch (message.function) {
- case GET_POWER_STATUS:
- device.status = this.getPowerStatus(message)
- break
- default:
- break
- }
- break
- default:
- if (status === (device.onlineStatus === 1 ? 'online' : 'offline')) {
- return
- }
- this.refreshDevices()
- break
- }
- }
- },
- getPowerStatus (message) {
- const { success, data } = getPowerStatusByMessage(message)
- if (success && data.length) {
- return data[0].switchStatus
- }
- return -1
- },
- 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 }) => {
- const map = {}
- const ids = []
- options.list = data.sort(this.sort).map(device => {
- if (device.onlineStatus === 1) {
- device.status = -2
- ids.push(device.id)
- } else {
- device.status = -1
- }
- device.flag = 0
- map[device.id] = device
- return device
- })
- options.power = ids
- options.map = map
- options.loaded = true
- this.getStatusReport(options)
- 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>
|