Dashboard.vue 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  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 c-count__title u-color--black u-bold u-ellipsis"
  9. :class="{ 'has-active u-pointer': multi }"
  10. @click="onChooseDepartment"
  11. >
  12. <i
  13. v-if="loading"
  14. class="el-icon-loading"
  15. />
  16. {{ group.name }}
  17. </div>
  18. <div class="l-flex__none c-count__item u-color--black u-bold u-text--center">
  19. <div>总数</div>
  20. <i
  21. v-if="monitor.loading"
  22. class="el-icon-loading"
  23. />
  24. <div v-else>{{ monitor.total }}</div>
  25. </div>
  26. <div class="l-flex__none c-count__item u-color--success dark u-bold u-text--center">
  27. <div>● 在线</div>
  28. <i
  29. v-if="monitor.loading"
  30. class="el-icon-loading"
  31. />
  32. <div v-else>{{ monitor.online }}</div>
  33. </div>
  34. <div class="l-flex__none c-count__item u-color--error dark u-bold u-text--center">
  35. <div>● 离线</div>
  36. <i
  37. v-if="monitor.loading"
  38. class="el-icon-loading"
  39. />
  40. <div v-else>{{ monitor.offline }}</div>
  41. </div>
  42. <div class="l-flex__none c-count__item u-color--info u-bold u-text--center">
  43. <div>● 未启用</div>
  44. <i
  45. v-if="monitor.loading"
  46. class="el-icon-loading"
  47. />
  48. <div v-else>{{ monitor.inactive }}</div>
  49. </div>
  50. <i
  51. class="el-icon-refresh o-icon md u-color--blue has-active u-pointer"
  52. @click="onRefresh"
  53. />
  54. </div>
  55. <div
  56. v-if="__PLACEHOLDER__"
  57. class="l-flex__none c-sibling-item--v far c-cards"
  58. >
  59. <card
  60. class="c-cards__item"
  61. type="info"
  62. title="总次数"
  63. tip="100"
  64. />
  65. <card
  66. class="c-cards__item"
  67. type="safety"
  68. title="安全异常"
  69. desc="累计异常数:30"
  70. />
  71. <card
  72. class="c-cards__item"
  73. type="performance"
  74. title="性能异常"
  75. desc="累计异常数:30"
  76. count="3"
  77. />
  78. <card
  79. class="c-cards__item"
  80. type="log"
  81. title="日志异常"
  82. desc="累计异常数:30"
  83. count="10"
  84. />
  85. <card
  86. class="c-cards__item"
  87. type="date"
  88. title="即将空闲排期"
  89. count="100"
  90. />
  91. </div>
  92. <div class="l-flex__auto l-grid--info c-sibling-item--v far u-overflow-y--auto">
  93. <device
  94. v-for="item in deviceOptions.list"
  95. :key="item.id"
  96. :device="item"
  97. />
  98. </div>
  99. <div
  100. v-if="!deviceOptions.loaded"
  101. class="c-sibling-item--v far u-text--center"
  102. >
  103. <i class="el-icon-loading" />
  104. </div>
  105. <el-drawer
  106. title="个人设置"
  107. :visible.sync="drawer"
  108. direction="ltr"
  109. :size="480"
  110. :with-header="false"
  111. append-to-body
  112. >
  113. <department-tree
  114. class="c-department-tree"
  115. :tree-data="groups"
  116. @change="onGroupChanged"
  117. />
  118. </el-drawer>
  119. </wrapper>
  120. </template>
  121. <script>
  122. import { mapGetters } from 'vuex'
  123. import {
  124. subscribe,
  125. unsubscribe
  126. } from '@/utils/mqtt'
  127. import { getDepartmentTree } from '@/api/user'
  128. import {
  129. getDevicesByAdmin,
  130. getDeviceStatisticsByCustom
  131. } from '@/api/device'
  132. import Card from './components/Card'
  133. import Device from './components/Device'
  134. export default {
  135. name: 'Dashboard',
  136. components: {
  137. Card,
  138. Device
  139. },
  140. data () {
  141. return {
  142. monitor: { loading: true },
  143. deviceOptions: {
  144. list: [],
  145. loaded: false
  146. },
  147. loading: true,
  148. loaded: false,
  149. multi: true,
  150. drawer: false,
  151. groups: null,
  152. group: null
  153. }
  154. },
  155. computed: {
  156. ...mapGetters(['tenant', 'org'])
  157. },
  158. created () {
  159. this.$timer = -1
  160. subscribe([
  161. '+/+/online',
  162. '+/+/offline',
  163. '+/+/calendar/update'
  164. ], this.onMessage)
  165. this.getDepartmentTree()
  166. this.group = { path: this.org, name: '我的部门' }
  167. this.refreshDevices(true)
  168. },
  169. beforeDestroy () {
  170. clearTimeout(this.$timer)
  171. this.monitor = { loading: true }
  172. unsubscribe([
  173. '+/+/online',
  174. '+/+/offline',
  175. '+/+/calendar/update'
  176. ], this.onMessage)
  177. },
  178. methods: {
  179. getDepartmentTree (isClick) {
  180. this.loading = true
  181. getDepartmentTree().finally(() => {
  182. this.loading = false
  183. }).then(({ data }) => {
  184. this.loaded = true
  185. if (data[0]?.children?.length) {
  186. this.groups = data
  187. if (isClick) {
  188. this.drawer = true
  189. }
  190. } else {
  191. this.multi = false
  192. }
  193. })
  194. },
  195. onGroupChanged ({ path, name }) {
  196. if (this.group.path !== path) {
  197. this.group = { path, name }
  198. this.refreshDevices(true)
  199. this.drawer = false
  200. }
  201. },
  202. onMessage (topic) {
  203. if (!this.deviceOptions.loaded) {
  204. return
  205. }
  206. const result = /^\d+\/(\d+)\/(online|offline)$/.exec(topic)
  207. if (!result) {
  208. return
  209. }
  210. const deviceId = result[1]
  211. const status = result[2]
  212. const device = this.deviceOptions.list.find(({ id }) => id === deviceId)
  213. if (device) {
  214. const onlineStatus = device.onlineStatus === 1 ? 'online' : 'offline'
  215. if (status === onlineStatus) {
  216. return
  217. }
  218. this.refreshDevices()
  219. }
  220. },
  221. onRefresh () {
  222. this.refreshDevices()
  223. },
  224. refreshDevices (force) {
  225. if (!force && this.monitor.loading) {
  226. return
  227. }
  228. const monitor = {
  229. loading: true,
  230. total: '-',
  231. online: '-',
  232. offline: '-',
  233. inactive: '-'
  234. }
  235. this.monitor = monitor
  236. clearTimeout(this.$timer)
  237. this.deviceOptions = { loaded: false }
  238. getDeviceStatisticsByCustom(
  239. this.group.path === this.tenant
  240. ? { tenant: this.group.path }
  241. : { org: this.group.path }
  242. ).then(({ data }) => {
  243. const { deactivatedTotal, notConnectedTotal, offLineTotal, onLineTotal, total } = data
  244. monitor.total = total
  245. monitor.online = onLineTotal
  246. monitor.offline = offLineTotal + notConnectedTotal
  247. monitor.inactive = deactivatedTotal
  248. }).finally(() => {
  249. monitor.loading = false
  250. if (!this.monitor.loading) {
  251. this.getDevices(this.monitor.total - this.monitor.inactive)
  252. }
  253. })
  254. },
  255. sort (a, b) {
  256. if (a.onlineStatus === b.onlineStatus) {
  257. return a.createTime <= b.createTime ? 1 : -1
  258. }
  259. return a.onlineStatus === 1 ? -1 : 1
  260. },
  261. getDevices (total) {
  262. if (!total || total === '-') {
  263. this.deviceOptions = { list: [], loaded: true }
  264. return
  265. }
  266. const options = { list: [], loaded: false }
  267. this.deviceOptions = options
  268. const query = {
  269. pageNum: 1,
  270. pageSize: total,
  271. activate: 1
  272. }
  273. if (this.group.path === this.tenant) {
  274. query.tenant = this.group.path
  275. } else {
  276. query.org = this.group.path
  277. }
  278. getDevicesByAdmin(query, { custom: true }).then(
  279. ({ data }) => {
  280. options.list = data.sort(this.sort)
  281. options.loaded = true
  282. },
  283. ({ isCancel }) => {
  284. if (!isCancel && !this.monitor.loading) {
  285. this.$timer = setTimeout(total => {
  286. this.getDevices(total)
  287. }, 2000, total)
  288. }
  289. }
  290. )
  291. },
  292. onChooseDepartment () {
  293. if (this.loaded) {
  294. if (this.multi) {
  295. this.drawer = true
  296. }
  297. } else if (!this.loading) {
  298. this.getDepartmentTree(true)
  299. }
  300. }
  301. }
  302. }
  303. </script>
  304. <style lang="scss" scoped>
  305. .c-department-tree {
  306. height: 100%;
  307. padding: $spacing--xs;
  308. }
  309. .c-count {
  310. justify-content: space-between;
  311. padding: $spacing--xs $spacing;
  312. border-radius: $radius;
  313. background-color: #fff;
  314. &__title {
  315. max-width: 200px;
  316. }
  317. &__item > div:first-child {
  318. margin-bottom: 10px;
  319. }
  320. }
  321. .c-cards {
  322. display: grid;
  323. grid-template-columns: repeat(5, 1fr);
  324. grid-column-gap: $spacing;
  325. &__item {
  326. min-width: 0;
  327. }
  328. }
  329. </style>