Dashboard.vue 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. <template>
  2. <wrapper
  3. fill
  4. margin
  5. >
  6. <div class="l-flex__none l-flex--row c-sibling-item--v c-count">
  7. <div
  8. class="l-flex__none u-color--black u-width--sm u-bold u-ellipsis has-active"
  9. @click="onChooseDepartment"
  10. >
  11. <i
  12. v-if="loading"
  13. class="el-icon-loading"
  14. />
  15. {{ group.name }}
  16. </div>
  17. <div class="l-flex__none c-count__item u-color--black u-bold u-text--center">
  18. <div>总数</div>
  19. <i
  20. v-if="monitor.loading"
  21. class="el-icon-loading"
  22. />
  23. <div v-else>{{ monitor.total }}</div>
  24. </div>
  25. <div class="l-flex__none c-count__item u-color--success dark u-bold u-text--center">
  26. <div>● 在线</div>
  27. <i
  28. v-if="monitor.loading"
  29. class="el-icon-loading"
  30. />
  31. <div v-else>{{ monitor.online }}</div>
  32. </div>
  33. <div class="l-flex__none c-count__item u-color--error dark u-bold u-text--center">
  34. <div>● 离线</div>
  35. <i
  36. v-if="monitor.loading"
  37. class="el-icon-loading"
  38. />
  39. <div v-else>{{ monitor.offline }}</div>
  40. </div>
  41. <div class="l-flex__none c-count__item u-color--info u-bold u-text--center">
  42. <div>● 未启用</div>
  43. <i
  44. v-if="monitor.loading"
  45. class="el-icon-loading"
  46. />
  47. <div v-else>{{ monitor.inactive }}</div>
  48. </div>
  49. <i
  50. class="el-icon-refresh o-icon md u-color--blue has-active"
  51. @click="onRefresh"
  52. />
  53. </div>
  54. <div
  55. ref="deviceContainer"
  56. class="l-flex__auto l-grid--info c-sibling-item--v far u-overflow-y--auto"
  57. >
  58. <device
  59. v-for="item in deviceOptions.list"
  60. :key="item.id"
  61. :device="item"
  62. :observer="observerDom"
  63. :flag="item.flag"
  64. />
  65. </div>
  66. <div
  67. v-if="!deviceOptions.loaded"
  68. class="c-sibling-item--v far u-text--center"
  69. >
  70. <i class="el-icon-loading" />
  71. </div>
  72. <department-drawer
  73. ref="departmentDrawer"
  74. remember
  75. @change="onGroupChanged"
  76. @loaded="onGroupLoaded"
  77. />
  78. </wrapper>
  79. </template>
  80. <script>
  81. import {
  82. subscribe,
  83. unsubscribe
  84. } from '@/utils/mqtt'
  85. import { ScreenshotCache } from '@/utils/cache'
  86. import {
  87. getDevicesByQuery,
  88. getDeviceStatisticsByPath
  89. } from '@/api/device'
  90. import Device from './components/Device'
  91. export default {
  92. name: 'Dashboard',
  93. components: {
  94. Device
  95. },
  96. data () {
  97. return {
  98. monitor: { loading: true },
  99. deviceOptions: {
  100. list: [],
  101. loaded: false
  102. },
  103. loading: false,
  104. group: {}
  105. }
  106. },
  107. created () {
  108. this.$timer = -1
  109. subscribe([
  110. '+/+/online',
  111. '+/+/offline',
  112. '+/+/calendar/update'
  113. ], this.onMessage)
  114. },
  115. beforeDestroy () {
  116. ScreenshotCache.clear()
  117. clearTimeout(this.$timer)
  118. this.monitor = { loading: true }
  119. unsubscribe([
  120. '+/+/online',
  121. '+/+/offline',
  122. '+/+/calendar/update'
  123. ], this.onMessage)
  124. if (this.$observer) {
  125. this.$observer.disconnect()
  126. this.$observer = null
  127. }
  128. },
  129. methods: {
  130. observerDom (el) {
  131. if (!this.$observer) {
  132. this.$observer = new IntersectionObserver(entries => {
  133. console.log('observer')
  134. entries.forEach(entry => {
  135. const inst = entry.target.__vue__
  136. if (inst && inst.intoView && inst.outView) {
  137. if (entry.isIntersecting) {
  138. inst.intoView()
  139. } else {
  140. inst.outView()
  141. }
  142. } else {
  143. const device = this.deviceOptions.map?.[entry.target.dataset.id]
  144. if (device) {
  145. if (entry.isIntersecting) {
  146. !device.flag && (device.flag = Date.now())
  147. } else {
  148. device.flag && (device.flag = 0)
  149. }
  150. }
  151. }
  152. })
  153. }, {
  154. root: this.$refs.deviceContainer,
  155. threshold: [0.25]
  156. })
  157. }
  158. this.$observer.observe(el)
  159. },
  160. onMessage (topic) {
  161. if (!this.deviceOptions.loaded) {
  162. return
  163. }
  164. const result = /^\d+\/(\d+)\/(online|offline|calendar\/update)$/.exec(topic)
  165. if (!result) {
  166. return
  167. }
  168. const deviceId = result[1]
  169. const topicKey = result[2]
  170. const device = this.deviceOptions.map?.[deviceId]
  171. if (device) {
  172. switch (topicKey) {
  173. case 'calendar/update':
  174. device.flag = -Date.now()
  175. break
  176. default:
  177. if (topicKey === (device.onlineStatus === 1 ? 'online' : 'offline')) {
  178. return
  179. }
  180. this.refreshDevices()
  181. break
  182. }
  183. }
  184. },
  185. onGroupLoaded () {
  186. this.loading = false
  187. },
  188. onGroupChanged ({ path, name }) {
  189. if (!this.group || this.group.path !== path) {
  190. this.group = { path, name }
  191. this.refreshDevices(true)
  192. }
  193. },
  194. onChooseDepartment () {
  195. this.$refs.departmentDrawer.show().then(visible => {
  196. this.loading = !visible
  197. })
  198. },
  199. onRefresh () {
  200. this.refreshDevices()
  201. },
  202. refreshDevices (force) {
  203. if (!force && this.monitor.loading) {
  204. return
  205. }
  206. const monitor = {
  207. loading: true,
  208. total: '-',
  209. online: '-',
  210. offline: '-',
  211. inactive: '-'
  212. }
  213. this.monitor = monitor
  214. clearTimeout(this.$timer)
  215. this.deviceOptions = { loaded: false }
  216. this.$observer?.disconnect()
  217. getDeviceStatisticsByPath(this.group.path).then(({ data }) => {
  218. const { deactivatedTotal, notConnectedTotal, offLineTotal, onLineTotal, total } = data
  219. monitor.total = total
  220. monitor.online = onLineTotal
  221. monitor.offline = offLineTotal + notConnectedTotal
  222. monitor.inactive = deactivatedTotal
  223. }).finally(() => {
  224. monitor.loading = false
  225. if (!this.monitor.loading) {
  226. this.getDevices(this.monitor.total - this.monitor.inactive)
  227. }
  228. })
  229. },
  230. sort (a, b) {
  231. if (a.onlineStatus === b.onlineStatus) {
  232. return a.createTime <= b.createTime ? 1 : -1
  233. }
  234. return a.onlineStatus === 1 ? -1 : 1
  235. },
  236. getDevices (total) {
  237. if (!total || total === '-') {
  238. this.deviceOptions = { list: [], loaded: true }
  239. return
  240. }
  241. const options = { list: [], loaded: false, power: [] }
  242. this.deviceOptions = options
  243. getDevicesByQuery({
  244. pageNum: 1,
  245. pageSize: total,
  246. activate: 1,
  247. org: this.group.path
  248. }, { custom: true }).then(
  249. ({ data }) => {
  250. const map = {}
  251. options.list = data.sort(this.sort).map(device => {
  252. device.flag = 0
  253. map[device.id] = device
  254. return device
  255. })
  256. options.map = map
  257. options.loaded = true
  258. },
  259. ({ isCancel }) => {
  260. if (!isCancel && !this.monitor.loading) {
  261. this.$timer = setTimeout(total => {
  262. this.getDevices(total)
  263. }, 2000, total)
  264. }
  265. }
  266. )
  267. }
  268. }
  269. }
  270. </script>
  271. <style lang="scss" scoped>
  272. .c-count {
  273. justify-content: space-between;
  274. padding: $spacing--xs $spacing;
  275. border-radius: $radius;
  276. background-color: #fff;
  277. &__item > div:first-child {
  278. margin-bottom: 10px;
  279. }
  280. }
  281. </style>