dashboard.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697
  1. <template>
  2. <div class="home-container">
  3. <el-row :gutter="15" class="home-card-one mb15">
  4. <el-col :xs="24" :sm="12" :md="12" :lg="6" :xl="6" v-for="(v, k) in homeOne" :key="k">
  5. <div class="home-card-top-part">
  6. <div class="top">
  7. <img :src="'/imgs/' + v.icoimg" class="icoimg" />
  8. <div class="card-right">
  9. <span class="font30">{{ v.allnum }}</span>
  10. <div class="label">{{ $t('message.dashboard.'+v.num3) }}</div>
  11. </div>
  12. </div>
  13. <div class="divider"></div>
  14. <div class="card-bottom">
  15. <div class="flex" style="gap: 10px">
  16. <img src="/@/assets/ok.svg" v-if="k < 2" alt="" class="icon" />
  17. <img src="/@/assets/date.svg" v-else alt="" class="icon" />
  18. <span class="info" :style="{ color: v.title1_bgcolor }">{{ $t('message.dashboard.'+v.title1) }}</span>
  19. <div class="num">{{ v.num1 }}</div>
  20. </div>
  21. <div class="split"></div>
  22. <div class="flex" style="gap: 10px">
  23. <img src="/@/assets/stop.svg" v-if="k < 2" alt="" class="icon" />
  24. <img src="/@/assets/today.svg" v-else alt="" class="icon" />
  25. <span class="info" :style="{ color: v.title2_bgcolor }">{{ $t('message.dashboard.'+v.title2) }}</span>
  26. <div class="num">{{ v.num2 }}</div>
  27. </div>
  28. </div>
  29. </div>
  30. </el-col>
  31. </el-row>
  32. <div class="chart-wrapper">
  33. <div class="chart-item" style="flex: 2">
  34. <div class="chart-title">{{ t('message.dashboard.设备消息') }}</div>
  35. <VueUiXy :dataset="dataset" :config="config" />
  36. </div>
  37. <div class="chart-item">
  38. <div class="chart-title">{{ t('message.dashboard.warningType') }}</div>
  39. <VueUiDonut :dataset="pieDataset" :config="pieConfig" />
  40. </div>
  41. </div>
  42. <AlarmList></AlarmList>
  43. </div>
  44. </template>
  45. <script lang="ts" setup>
  46. import { toRefs, reactive, onMounted, ref, watch, nextTick, onActivated, getCurrentInstance, onUnmounted } from 'vue'
  47. import { useRouter } from 'vue-router'
  48. import { VueUiXy, VueUiDonut } from 'vue-data-ui'
  49. import 'vue-data-ui/style.css'
  50. import { getLineData, getPieData } from '/@/utils/dataUiOptions'
  51. import api from '/@/api/datahub'
  52. import dayjs from 'dayjs'
  53. import AlarmList from '/@/views/iot/alarm/list/index.vue'
  54. import { useThemeChange } from '/@/hooks/useCommon'
  55. import { store } from '/@/store/index';
  56. import { useI18n } from 'vue-i18n'
  57. const { t } = useI18n()
  58. // 页面是显示状态
  59. let isActice = true
  60. //#region 线图
  61. // 获取默认图形配置数据
  62. const chartData = getLineData({
  63. xAxis: [],
  64. legend: [t('message.dashboard.messageCount'), t('message.dashboard.warningCount')],
  65. datas: [[], []],
  66. })
  67. const config = ref<any>(chartData.config)
  68. const dataset = ref<any[]>(chartData.dataset)
  69. //#endregion
  70. //#region 饼图
  71. // 获取默认图形配置数据
  72. const pieData = getPieData({
  73. legend: [' '],
  74. datas: [[]],
  75. })
  76. const pieConfig = ref<any>(pieData.config)
  77. const pieDataset = ref<any[]>(pieData.dataset)
  78. // 监听暗黑模式变化,将 vue-data-ui 的 config 传进来,就能自动更新主题
  79. useThemeChange([config, pieConfig])
  80. //#endregion
  81. onUnmounted(() => {
  82. isActice = false
  83. })
  84. const { proxy } = getCurrentInstance() as any
  85. const { alarm_type } = proxy.useDict('alarm_type')
  86. const alarmTypeMap: any = {}
  87. // 监听告警类型是否获取成功
  88. watch(
  89. () => alarm_type.value,
  90. (list) => {
  91. if (!list.length) return
  92. list.forEach((item: any) => {
  93. alarmTypeMap[item.value] = t('message.dashboard.'+item.label)
  94. })
  95. // 预警类型需要类型返回后才能请求
  96. getDeviceAlarmLevelCount()
  97. },
  98. {
  99. immediate: true,
  100. }
  101. )
  102. watch(() => store.state.themeConfig.themeConfig.globalI18n, (globalI18n) => {
  103. if(globalI18n) {
  104. // 更新圖表數據的圖例
  105. dataset.value[0].name = t('message.dashboard.messageCount')
  106. dataset.value[1].name = t('message.dashboard.warningCount')
  107. // 更新餅圖數據 - 重新生成圖例標籤
  108. if (pieDataset.value && pieDataset.value.length > 0) {
  109. // 更新 alarmTypeMap 中的翻譯
  110. alarm_type.value.forEach((item: any) => {
  111. alarmTypeMap[item.value] = t('message.dashboard.'+item.label)
  112. })
  113. // 重新調用API獲取最新數據並重新渲染
  114. getDeviceAlarmLevelCount()
  115. }
  116. }
  117. });
  118. const editDicRef = ref()
  119. const detailRef = ref()
  120. const router = useRouter()
  121. const state = reactive({
  122. loading: false,
  123. tableData: {
  124. data: [],
  125. total: 0,
  126. loading: false,
  127. param: {
  128. pageNum: 1,
  129. pageSize: 20,
  130. status: '',
  131. dateRange: [],
  132. },
  133. },
  134. homeOne: [
  135. {
  136. allnum: 0,
  137. num1: 0,
  138. num2: 0,
  139. // num3: t('message.dashboard.product'),
  140. num3: '产品',
  141. num4: 'icon-zidingyibuju',
  142. color1: '#6690F9',
  143. color2: '--el-color-warning-lighter',
  144. color3: '--el-color-warning',
  145. icoimg: 'dashboard-icon1.svg',
  146. title1: '启用',
  147. title2: '停用',
  148. title1_bgcolor: '#3cd357',
  149. title2_bgcolor: '#FFBB73',
  150. },
  151. {
  152. allnum: 0,
  153. num1: 0,
  154. num2: 0,
  155. // num3: t('message.dashboard.onlineDevice'),
  156. num3: '在线设备',
  157. num4: 'icon-putong',
  158. color1: '#FF6462',
  159. color2: '--next-color-primary-lighter',
  160. color3: '--el-color-primary',
  161. icoimg: 'dashboard-icon2.svg',
  162. title1: '启用',
  163. title2: '停用',
  164. title1_bgcolor: '#3cd357',
  165. title2_bgcolor: '#FFBB73',
  166. },
  167. {
  168. allnum: 0,
  169. num1: 0,
  170. num2: 0,
  171. // num3: t('message.dashboard.deviceMessage'),
  172. num3: '设备消息',
  173. num4: 'icon-shidu',
  174. color1: '#6690F9',
  175. color2: '--el-color-success-lighter',
  176. color3: '--el-color-success',
  177. icoimg: 'dashboard-icon3.svg',
  178. title1: "本月",
  179. title2: "今日",
  180. title1_bgcolor: '#4285F4',
  181. title2_bgcolor: '#FFBB73',
  182. },
  183. {
  184. allnum: 0,
  185. num1: 0,
  186. num2: 0,
  187. // num3: t('message.dashboard.deviceWarning'),
  188. num3: '设备警告',
  189. num4: 'icon-zaosheng',
  190. color1: '#6690F9',
  191. color2: '--el-color-warning-lighter',
  192. color3: '--el-color-warning',
  193. icoimg: 'dashboard-icon4.svg',
  194. title1: '本月',
  195. title2: '今日',
  196. title1_bgcolor: '#4285F4',
  197. title2_bgcolor: '#FFBB73',
  198. },
  199. ],
  200. myCharts: [],
  201. charts: {
  202. theme: '',
  203. bgColor: '',
  204. color: '#303133',
  205. },
  206. })
  207. const { loading, tableData, homeOne } = toRefs(state)
  208. // 定时获取设备,在线信息,告警数量更新
  209. const getOverviewData = () => {
  210. getProductCount()
  211. getDeviceDataTotalCount()
  212. getDeviceDataTotalCountMonth()
  213. getDeviceDataTotalCountDay()
  214. getDeviceOnlineOfflineCount()
  215. getDeviceAlarmLevelCountYear()
  216. getDeviceAlarmLevelCountMonth()
  217. getDeviceAlarmLevelCountDay()
  218. // 图形数据
  219. getDeviceDataCount()
  220. }
  221. // 普通数据3秒更新
  222. const intervalTimeLong = 3000
  223. function loopRquest(fun: Function, timeLong?: number) {
  224. setTimeout(() => {
  225. isActice && fun()
  226. }, timeLong || intervalTimeLong)
  227. }
  228. // 获取告警告警数量和消息数量绘图
  229. function getDeviceDataCount() {
  230. // 获取年度消息,年度告警数量
  231. Promise.all([api.iotManage.deviceDataCount('year'), api.iotManage.deviceAlertCountByYearMonth(dayjs().format('YYYY'))])
  232. .then(([msg, alarm]: any) => {
  233. const msgArr = msg?.data || []
  234. const alarmArr = alarm?.data || []
  235. const dataNull = new Array(12).fill(0)
  236. const month = dataNull.map((_, i) => i + 1)
  237. const list1 = [...dataNull]
  238. const list2 = [...dataNull]
  239. msgArr.forEach((item: any) => {
  240. list1[item.Title - 1] = item.Value
  241. })
  242. alarmArr.forEach((item: any) => {
  243. list2[item.Title - 1] = item.Value
  244. })
  245. const chartData = getLineData({
  246. xAxis: month,
  247. legend: [t('message.dashboard.messageCount'), t('message.dashboard.warningCount')],
  248. datas: [list1, list2],
  249. })
  250. config.value = chartData.config
  251. dataset.value = chartData.dataset
  252. })
  253. // .finally(() => loopRquest(getDeviceDataCount, 60000))
  254. }
  255. // 获取告警告警数量和消息数量绘图
  256. function getDeviceAlarmLevelCount() {
  257. // 按告警级别统计 绘制饼图
  258. api.iotManage
  259. .deviceAlarmLevelCount('year', dayjs().format('YYYY'))
  260. .then((res: any) => {
  261. const list = (res.data || []).sort((a: any, b: any) => b.Title - a.Title)
  262. if (list.length) {
  263. const pieData = getPieData({
  264. legend: list.map((item: any) => alarmTypeMap[item.Title]),
  265. types: list.map((item: any) => item.Title),
  266. datas: list.map((item: any) => [item.Value]),
  267. totalText: t('message.dashboard.total')
  268. })
  269. pieConfig.value = pieData.config
  270. pieDataset.value = pieData.dataset
  271. }
  272. })
  273. .finally(() => loopRquest(getDeviceAlarmLevelCount, 60000))
  274. }
  275. // 产品数量
  276. function getProductCount() {
  277. api.iotManage
  278. .productCount()
  279. .then((res: any) => {
  280. state.homeOne[0].allnum = res.total
  281. state.homeOne[0].num1 = res.enable
  282. state.homeOne[0].num2 = res.disable
  283. })
  284. .finally(() => loopRquest(getProductCount))
  285. }
  286. // 设备数据总数
  287. function getDeviceDataTotalCount() {
  288. api.iotManage
  289. .deviceDataTotalCount('year')
  290. .then((res: any) => {
  291. state.homeOne[2].allnum = res.number
  292. })
  293. .finally(() => loopRquest(getDeviceDataTotalCount))
  294. }
  295. // 设备数据总数-月
  296. function getDeviceDataTotalCountMonth() {
  297. api.iotManage
  298. .deviceDataTotalCount('month')
  299. .then((res: any) => {
  300. state.homeOne[2].num1 = res.number
  301. })
  302. .finally(() => loopRquest(getDeviceDataTotalCountMonth))
  303. }
  304. // 设备数据总数-月
  305. function getDeviceDataTotalCountDay() {
  306. api.iotManage
  307. .deviceDataTotalCount('day')
  308. .then((res: any) => {
  309. state.homeOne[2].num2 = res.number
  310. })
  311. .finally(() => loopRquest(getDeviceDataTotalCountDay))
  312. }
  313. // 设备数量
  314. function getDeviceOnlineOfflineCount() {
  315. api.iotManage
  316. .deviceOnlineOfflineCount()
  317. .then((res: any) => {
  318. state.homeOne[1].allnum = res.online
  319. state.homeOne[1].num1 = res.total - res.disable
  320. state.homeOne[1].num2 = res.disable
  321. })
  322. .finally(() => loopRquest(getDeviceOnlineOfflineCount))
  323. }
  324. // 告警数量-年
  325. function getDeviceAlarmLevelCountYear() {
  326. api.iotManage
  327. .deviceAlarmLevelCount('year', dayjs().format('YYYY'))
  328. .then((res: any) => {
  329. const list = res.data || []
  330. const total = list.reduce((a: any, b: any) => a + b.Value, 0)
  331. state.homeOne[3].allnum = total
  332. })
  333. .finally(() => loopRquest(getDeviceAlarmLevelCountYear))
  334. }
  335. // 告警数量-月
  336. function getDeviceAlarmLevelCountMonth() {
  337. api.iotManage
  338. .deviceAlarmLevelCount('month', dayjs().format('M'))
  339. .then((res: any) => {
  340. const total = (res.data || []).reduce((a: any, b: any) => a + b.Value, 0)
  341. state.homeOne[3].num1 = total
  342. })
  343. .finally(() => loopRquest(getDeviceAlarmLevelCountMonth))
  344. }
  345. // 告警数量-日
  346. function getDeviceAlarmLevelCountDay() {
  347. api.iotManage
  348. .deviceAlarmLevelCount('day', dayjs().format('D'))
  349. .then((res: any) => {
  350. const total = (res.data || []).reduce((a: any, b: any) => a + b.Value, 0)
  351. state.homeOne[3].num2 = total
  352. })
  353. .finally(() => loopRquest(getDeviceAlarmLevelCountDay))
  354. }
  355. const getAlarmList = () => {
  356. api.iotManage.getAlarmList(state.tableData.param).then((res: any) => {
  357. state.tableData.data = res.list
  358. state.tableData.total = res.Total
  359. })
  360. }
  361. //打开详情页
  362. const onOpenDetailDic = (row: any) => {
  363. detailRef.value.openDialog(row)
  364. }
  365. // 打开修改产品弹窗
  366. const onOpenEditDic = (row: any) => {
  367. editDicRef.value.openDialog(row)
  368. }
  369. // 告警信息-更多信息
  370. const toMore = () => {
  371. router.push({ path: '/iotmanager/alarm/log' })
  372. }
  373. // 页面加载时
  374. onMounted(() => {
  375. getOverviewData()
  376. getAlarmList()
  377. })
  378. </script>
  379. <style scoped lang="scss">
  380. $homeNavLengh: 8;
  381. .chart-wrapper {
  382. display: flex;
  383. justify-content: space-between;
  384. align-items: stretch;
  385. gap: 16px;
  386. .chart-item {
  387. background-color: var(--el-color-white);
  388. padding: 12px 15px;
  389. border-radius: 8px;
  390. margin-bottom: 16px;
  391. flex: 1;
  392. min-width: 200px;
  393. }
  394. .chart-title {
  395. font-size: 15px;
  396. font-weight: bold;
  397. padding-left: 5px;
  398. }
  399. }
  400. .home-card-top-part {
  401. background-color: var(--el-color-white);
  402. border-radius: 8px;
  403. padding: 20px 20px;
  404. .top {
  405. display: flex;
  406. justify-content: space-around;
  407. overflow: hidden;
  408. align-items: center;
  409. }
  410. .icoimg {
  411. width: 54px !important;
  412. height: 54px !important;
  413. margin-right: 12px;
  414. }
  415. .label {
  416. font-size: 14px;
  417. font-weight: 500;
  418. }
  419. .divider {
  420. border-top: 1px solid var(--el-border-color-light);
  421. margin: 12px 0 15px;
  422. }
  423. .card-right {
  424. flex: 1;
  425. display: flex;
  426. flex-direction: column;
  427. justify-content: space-between;
  428. white-space: nowrap;
  429. line-height: 1;
  430. height: 54px;
  431. .font30 {
  432. color: #4285f4;
  433. font-weight: bold;
  434. font-size: 30px;
  435. }
  436. }
  437. .card-bottom {
  438. font-size: 12px;
  439. display: flex;
  440. align-items: center;
  441. justify-content: space-around;
  442. gap: 12px;
  443. white-space: nowrap;
  444. .split {
  445. border-right: 1px solid var(--el-border-color-light);
  446. height: 20px;
  447. }
  448. .icon {
  449. width: 17px;
  450. height: 17px;
  451. }
  452. .info {
  453. font-size: 12px;
  454. font-weight: 500;
  455. }
  456. }
  457. }
  458. .home-container {
  459. overflow: hidden;
  460. .home-card-one,
  461. .home-card-two,
  462. .home-card-three {
  463. .icoimg {
  464. width: 75px;
  465. height: 75px;
  466. }
  467. .title_status {
  468. width: 7px;
  469. height: 7px;
  470. background: #c1bbbb;
  471. border-radius: 50px;
  472. margin-right: 5px;
  473. }
  474. .home-card-item,
  475. .home-card-top {
  476. width: 100%;
  477. border-radius: 8px;
  478. transition: all ease 0.3s;
  479. padding: 10px 20px;
  480. overflow: hidden;
  481. background: var(--el-color-white);
  482. color: var(--el-text-color-primary);
  483. // border: 1px solid var(--next-border-color-light);
  484. &:hover {
  485. // box-shadow: 0 2px 12px var(--next-color-dark-hover);
  486. transition: all ease 0.3s;
  487. }
  488. &-icon {
  489. width: 70px;
  490. height: 70px;
  491. border-radius: 100%;
  492. flex-shrink: 1;
  493. i {
  494. color: var(--el-text-color-placeholder);
  495. }
  496. }
  497. &-title {
  498. font-size: 15px;
  499. font-weight: bold;
  500. height: 30px;
  501. }
  502. }
  503. }
  504. .home-card-three {
  505. .home-card-item-title {
  506. display: flex;
  507. justify-content: space-between;
  508. // span:nth-child(2) {
  509. // color: #409eff;
  510. // }
  511. }
  512. }
  513. .home-card-one {
  514. @for $i from 0 through 3 {
  515. .home-one-animation#{$i} {
  516. opacity: 0;
  517. animation-name: error-num;
  518. animation-duration: 0.5s;
  519. animation-fill-mode: forwards;
  520. animation-delay: calc($i/10) + s;
  521. }
  522. }
  523. }
  524. .home-card-two,
  525. .home-card-three {
  526. .home-card-top {
  527. height: 250px;
  528. .box-card {
  529. padding: 15px 20px 20px 10px;
  530. p {
  531. margin-bottom: 10px;
  532. }
  533. &-item {
  534. margin-bottom: 10px;
  535. }
  536. }
  537. }
  538. .home-card-item,
  539. .home-card-top {
  540. width: 100%;
  541. overflow: hidden;
  542. .home-monitor {
  543. height: 100%;
  544. .flex-warp-item {
  545. width: 25%;
  546. height: 111px;
  547. display: flex;
  548. .flex-warp-item-box {
  549. margin: auto;
  550. text-align: center;
  551. color: var(--el-text-color-primary);
  552. display: flex;
  553. border-radius: 5px;
  554. background: var(--next-bg-color);
  555. cursor: pointer;
  556. transition: all 0.3s ease;
  557. &:hover {
  558. background: var(--el-color-primary-light-9);
  559. transition: all 0.3s ease;
  560. }
  561. }
  562. @for $i from 0 through $homeNavLengh {
  563. .home-animation#{$i} {
  564. opacity: 0;
  565. animation-name: error-num;
  566. animation-duration: 0.5s;
  567. animation-fill-mode: forwards;
  568. animation-delay: calc($i/10) + s;
  569. }
  570. }
  571. }
  572. }
  573. }
  574. }
  575. .text-info {
  576. color: #23c6c8;
  577. }
  578. .text-danger {
  579. color: #ed5565;
  580. }
  581. .git-res {
  582. margin-top: 20px;
  583. }
  584. .git-res .el-link {
  585. margin-right: 30px;
  586. }
  587. ul,
  588. li {
  589. padding: 0;
  590. margin: 0;
  591. list-style: none;
  592. }
  593. .product {
  594. margin-top: 50px;
  595. h3 {
  596. margin-bottom: 15px;
  597. }
  598. }
  599. .product li {
  600. margin-bottom: 20px;
  601. float: left;
  602. width: 150px;
  603. }
  604. .box-card.xx {
  605. margin-top: 20px;
  606. }
  607. }
  608. .home-card-item.chart {
  609. padding: 10px !important;
  610. }
  611. </style>