dashboard.vue 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696
  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" :class="{ 'home-media home-media-lg': k > 1, 'home-media-sm': k === 1 }">
  5. <div class="home-card-item ">
  6. <div class="">{{ v.num3 }}</div>
  7. <div class="flex-margin flex w100" :class="` home-one-animation${k}`">
  8. <div class="flex-auto">
  9. <span class="font30">{{ v.allnum }}</span>
  10. </div>
  11. <div class="home-card-item-icon flex">
  12. <img :src="'/imgs/' + v.icoimg" class="icoimg">
  13. </div>
  14. </div>
  15. <div class="flex" style="font-weight: bold;">
  16. <div class="flex font14">
  17. <div class="title_status" :style="{ backgroundColor: v.title1_bgcolor }"></div> {{ v.title1 }} {{ v.num1 }}
  18. </div>
  19. <div class="flex ml20 font14">
  20. <div class="title_status" :style="{ backgroundColor: v.title2_bgcolor }"></div>{{ v.title2 }} {{ v.num2 }}
  21. </div>
  22. </div>
  23. </div>
  24. </el-col>
  25. </el-row>
  26. <el-row :gutter="15" class="home-card-two mb15">
  27. <el-col :xs="24" :sm="14" :md="14" :lg="16" :xl="16">
  28. <div class="home-card-item">
  29. <div style="height: 100%" ref="homeLineRef"></div>
  30. </div>
  31. </el-col>
  32. <el-col :xs="24" :sm="10" :md="10" :lg="8" :xl="8" class="home-media">
  33. <div class="home-card-item">
  34. <div style="height: 100%" ref="homePieRef"></div>
  35. </div>
  36. </el-col>
  37. </el-row>
  38. <el-row :gutter="15" class="home-card-three">
  39. <el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
  40. <div class="home-card-item" style="height: auto;">
  41. <div class="home-card-item-title">
  42. <span>告警信息列表</span>
  43. <el-button size="small" text type="primary" @click="toMore()">更多信息</el-button>
  44. </div>
  45. <el-table :data="tableData.data" style="width: 100%" v-loading="loading">
  46. <el-table-column label="ID" align="center" prop="id" width="60" v-col="'ID'" />
  47. <el-table-column label="告警类型" prop="type" :show-overflow-tooltip="true" v-col="'type'">
  48. <template #default="scope">
  49. <span v-if="scope.row.type == 1">规则告警</span>
  50. <span v-else>设备自主告警</span>
  51. </template>
  52. </el-table-column>
  53. <el-table-column label="规则名称" prop="ruleName" :show-overflow-tooltip="true" v-col="'ruleName'" />
  54. <el-table-column label="规则级别" prop="alarmLevel" :show-overflow-tooltip="true" v-col="'alarmLevel'">
  55. <template #default="scope">
  56. {{ scope.row.alarmLevel.name }}
  57. </template>
  58. </el-table-column>
  59. <el-table-column label="产品标识" prop="productKey" :show-overflow-tooltip="true" v-col="'productKey'" />
  60. <el-table-column label="设备标识" prop="deviceKey" :show-overflow-tooltip="true" v-col="'deviceKey'" />
  61. <el-table-column prop="status" label="告警状态" width="100" align="center" v-col="'status'">
  62. <template #default="scope">
  63. <el-tag type="success" size="small" v-if="scope.row.status">已处理</el-tag>
  64. <el-tag type="info" size="small" v-else>未处理</el-tag>
  65. </template>
  66. </el-table-column>
  67. <el-table-column prop="createdAt" label="告警时间" align="center" width="180" v-col="'createdAt'"></el-table-column>
  68. <el-table-column label="操作" width="150" align="center" fixed="right" v-col="'handle'">
  69. <template #default="scope">
  70. <el-button v-auth="'detail'" size="small" text type="primary" @click="onOpenDetailDic(scope.row)">详情</el-button>
  71. <el-button v-auth="'edit'" size="small" text type="warning" @click="onOpenEditDic(scope.row)" v-if="scope.row.status == 0">处理</el-button>
  72. </template>
  73. </el-table-column>
  74. </el-table>
  75. </div>
  76. </el-col>
  77. </el-row>
  78. <EditDic ref="editDicRef" @dataList="getAlarmList" />
  79. <DetailDic ref="detailRef" @dataList="getAlarmList" />
  80. </div>
  81. </template>
  82. <script lang="ts">
  83. import { toRefs, reactive, defineComponent, onMounted, ref, watch, nextTick, onActivated } from 'vue';
  84. import * as echarts from 'echarts';
  85. import { useRouter } from 'vue-router';
  86. import { useStore } from '/@/store/index';
  87. import api from '/@/api/datahub';
  88. import EditDic from '../alarm/log/component/edit.vue';
  89. import DetailDic from '../alarm/log/component/detail.vue';
  90. let global: any = {
  91. homeChartOne: null,
  92. homeChartTwo: null,
  93. homeCharThree: null,
  94. dispose: [null, '', undefined]
  95. };
  96. export default defineComponent({
  97. name: 'home',
  98. components: { EditDic, DetailDic },
  99. setup() {
  100. const editDicRef = ref();
  101. const detailRef = ref();
  102. const homeLineRef = ref();
  103. const homePieRef = ref();
  104. const homeBarRef = ref();
  105. const store = useStore();
  106. const router = useRouter();
  107. const state = reactive({
  108. loading: false,
  109. tableData: {
  110. data: [],
  111. total: 0,
  112. loading: false,
  113. param: {
  114. pageNum: 1,
  115. pageSize: 10,
  116. status: '',
  117. dateRange: [],
  118. },
  119. },
  120. homeOne: [
  121. {
  122. allnum: '0',
  123. num1: '0',
  124. num2: '0',
  125. num3: '产品',
  126. num4: 'icon-zidingyibuju',
  127. color1: '#6690F9',
  128. color2: '--el-color-warning-lighter',
  129. color3: '--el-color-warning',
  130. icoimg: 'index_product.svg',
  131. title1: '启用',
  132. title2: '停用',
  133. title1_bgcolor: '#3cd357',
  134. title2_bgcolor: '#c1bbbb',
  135. },
  136. {
  137. allnum: '0',
  138. num1: '0',
  139. num2: '0',
  140. num3: '设备',
  141. num4: 'icon-putong',
  142. color1: '#FF6462',
  143. color2: '--next-color-primary-lighter',
  144. color3: '--el-color-primary',
  145. icoimg: 'index_device.svg',
  146. title1: '在线',
  147. title2: '离线',
  148. title1_bgcolor: '#3cd357',
  149. title2_bgcolor: '#c1bbbb',
  150. },
  151. {
  152. allnum: '0',
  153. num1: '0',
  154. num2: '0',
  155. num3: '设备消息',
  156. num4: 'icon-shidu',
  157. color1: '#6690F9',
  158. color2: '--el-color-success-lighter',
  159. color3: '--el-color-success',
  160. icoimg: 'index_sensor.svg',
  161. title1: '总量',
  162. title2: '今日',
  163. title1_bgcolor: '#c1bbbb',
  164. title2_bgcolor: '#18f3ff',
  165. },
  166. {
  167. allnum: '0',
  168. num1: '0',
  169. num2: '0',
  170. num3: '设备告警',
  171. num4: 'icon-zaosheng',
  172. color1: '#6690F9',
  173. color2: '--el-color-warning-lighter',
  174. color3: '--el-color-warning',
  175. icoimg: 'index_alarm.svg',
  176. title1: '总量',
  177. title2: '新增',
  178. title1_bgcolor: '#c1bbbb',
  179. title2_bgcolor: '#ff1818',
  180. },
  181. ],
  182. myCharts: [],
  183. charts: {
  184. theme: '',
  185. bgColor: '',
  186. color: '#303133',
  187. },
  188. lineChartXAxisDat: [],
  189. lineChartMsgTotalData: [],
  190. lineChartAlarmTotalData: [],
  191. pieChartLegend: [],
  192. pieChartData: []
  193. });
  194. // 折线图
  195. const initLineChart = () => {
  196. if (!global.dispose.some((b: any) => b === global.homeChartOne)) global.homeChartOne.dispose();
  197. global.homeChartOne = <any>echarts.init(homeLineRef.value, state.charts.theme);
  198. const option = {
  199. backgroundColor: state.charts.bgColor,
  200. title: {
  201. text: '设备消息',
  202. x: 'left',
  203. textStyle: { fontSize: '15', color: state.charts.color },
  204. },
  205. grid: { top: 70, right: 20, bottom: 30, left: 30 },
  206. tooltip: { trigger: 'axis' },
  207. legend: { data: ['消息量', '预警量'], right: 0 },
  208. xAxis: {
  209. data: state.lineChartXAxisData
  210. },
  211. yAxis: [
  212. {
  213. type: 'value',
  214. name: '条数',
  215. splitLine: { show: true, lineStyle: { type: 'dashed', color: '#f5f5f5' } },
  216. axisLabel: {
  217. margin: 2,
  218. formatter: function (value, index) {
  219. if (value >= 10000 && value < 10000000) {
  220. value = value / 10000 + "W";
  221. } else if (value >= 10000000) {
  222. value = value / 10000000 + "KW";
  223. }
  224. return value;
  225. }
  226. },
  227. },
  228. ],
  229. series: [
  230. {
  231. name: '消息量',
  232. type: 'line',
  233. symbolSize: 6,
  234. symbol: 'circle',
  235. smooth: true,
  236. data: state.lineChartMsgTotalData,
  237. lineStyle: { color: '#fe9a8b' },
  238. itemStyle: { color: '#fe9a8b', borderColor: '#fe9a8b' },
  239. areaStyle: {
  240. color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
  241. { offset: 0, color: '#fe9a8bb3' },
  242. { offset: 1, color: '#fe9a8b03' },
  243. ]),
  244. },
  245. },
  246. {
  247. name: '预警量',
  248. type: 'line',
  249. symbolSize: 6,
  250. symbol: 'circle',
  251. smooth: true,
  252. data: state.lineChartAlarmTotalData,
  253. lineStyle: { color: '#9E87FF' },
  254. itemStyle: { color: '#9E87FF', borderColor: '#9E87FF' },
  255. areaStyle: {
  256. color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
  257. { offset: 0, color: '#9E87FFb3' },
  258. { offset: 1, color: '#9E87FF03' },
  259. ]),
  260. },
  261. emphasis: {
  262. itemStyle: {
  263. color: {
  264. type: 'radial',
  265. x: 0.5,
  266. y: 0.5,
  267. r: 0.5,
  268. colorStops: [
  269. { offset: 0, color: '#9E87FF' },
  270. { offset: 0.4, color: '#9E87FF' },
  271. { offset: 0.5, color: '#fff' },
  272. { offset: 0.7, color: '#fff' },
  273. { offset: 0.8, color: '#fff' },
  274. { offset: 1, color: '#fff' },
  275. ],
  276. },
  277. borderColor: '#9E87FF',
  278. borderWidth: 2,
  279. },
  280. },
  281. },
  282. ],
  283. };
  284. (<any>global.homeChartOne).setOption(option);
  285. (<any>state.myCharts).push(global.homeChartOne);
  286. };
  287. // 饼图
  288. const initPieChart = () => {
  289. if (!global.dispose.some((b: any) => b === global.homeChartTwo)) global.homeChartTwo.dispose();
  290. global.homeChartTwo = <any>echarts.init(homePieRef.value, state.charts.theme);
  291. var getname = state.pieChartLegend;
  292. var getvalue = state.pieChartData;
  293. var data = [];
  294. for (var i = 0; i < getname.length; i++) {
  295. data.push({ name: getname[i], value: getvalue[i] });
  296. }
  297. const colorList = ['#FF0000', '#FEC279', '#968AF5', '#51A3FC', '#36C78B',];
  298. const option = {
  299. backgroundColor: state.charts.bgColor,
  300. title: {
  301. text: '预警类型',
  302. x: 'left',
  303. textStyle: { fontSize: '15', color: state.charts.color },
  304. },
  305. tooltip: { trigger: 'item', formatter: '{b} <br/> {c}%' },
  306. graphic: {
  307. elements: [
  308. {
  309. type: 'image',
  310. z: -1,
  311. left: '16.5%',
  312. top: 'center',
  313. },
  314. ],
  315. },
  316. legend: {
  317. type: 'scroll',
  318. orient: 'vertical',
  319. right: '0%',
  320. left: '65%',
  321. top: 'center',
  322. itemWidth: 14,
  323. itemHeight: 14,
  324. data: getname,
  325. textStyle: {
  326. rich: {
  327. name: {
  328. fontSize: 14,
  329. fontWeight: 400,
  330. width: 200,
  331. height: 35,
  332. padding: [0, 0, 0, 60],
  333. color: state.charts.color,
  334. },
  335. rate: {
  336. fontSize: 15,
  337. fontWeight: 500,
  338. height: 35,
  339. width: 40,
  340. padding: [0, 0, 0, 30],
  341. color: state.charts.color,
  342. },
  343. },
  344. },
  345. },
  346. series: [
  347. {
  348. type: 'pie',
  349. radius: ['70', '90'],
  350. center: ['32%', '50%'],
  351. itemStyle: {
  352. color: function (params: any) {
  353. return colorList[params.dataIndex];
  354. },
  355. },
  356. label: { show: false },
  357. labelLine: { show: false },
  358. data: data,
  359. },
  360. ],
  361. };
  362. (<any>global.homeChartTwo).setOption(option);
  363. (<any>state.myCharts).push(global.homeChartTwo);
  364. };
  365. // 批量设置 echarts resize
  366. const initEchartsResizeFun = () => {
  367. nextTick(() => {
  368. for (let i = 0; i < state.myCharts.length; i++) {
  369. setTimeout(() => {
  370. (<any>state.myCharts[i]).resize();
  371. }, i * 1000);
  372. }
  373. });
  374. };
  375. // 批量设置 echarts resize
  376. const initEchartsResize = () => {
  377. window.addEventListener('resize', initEchartsResizeFun);
  378. };
  379. const getOverviewData = () => {
  380. api.iotManage.getOverviewData().then((res: any) => {
  381. const { overview, device, alarmLevel } = res;
  382. // overview
  383. // "deviceTotal": 8, //设备总量
  384. // "deviceOffline": 4, //离线设备数量
  385. // "productTotal": 6, //产品总量
  386. // "productAdded": 0, //今日产品增量
  387. // "msgTotal": 107246, //设备消息总量
  388. // "msgAdded": 7391, //今日设备消息增量
  389. // "alarmTotal": 43, //设备报警总量
  390. // "alarmAdded": 0 //今日设备报警增量
  391. state.homeOne[0].allnum = overview.productTotal;
  392. state.homeOne[0].num1 = `${overview.productActivation}`;
  393. state.homeOne[0].num2 = `${overview.productDeactivation}`;
  394. state.homeOne[1].allnum = overview.deviceTotal;
  395. state.homeOne[1].num1 = `${overview.deviceTotal - overview.deviceOffline}`;
  396. state.homeOne[1].num2 = `${overview.deviceOffline}`;
  397. state.homeOne[2].allnum = overview.msgTotal;
  398. state.homeOne[2].num1 = overview.msgTotal;
  399. state.homeOne[2].num2 = `${overview.msgAdded}`;
  400. state.homeOne[3].allnum = overview.alarmTotal;
  401. state.homeOne[3].num1 = overview.alarmTotal;
  402. state.homeOne[3].num2 = `${overview.alarmAdded}`;
  403. // device
  404. // msgTotal 设备消息量月度统计
  405. // alarmTotal 设备告警量月度统计
  406. state.lineChartMsgTotalData = [];
  407. state.lineChartAlarmTotalData = [];
  408. state.lineChartXAxisData = Object.keys(device.msgTotal).map((item: any) => {
  409. state.lineChartMsgTotalData.push(device.msgTotal[item]);
  410. state.lineChartAlarmTotalData.push(device.alarmTotal[item]);
  411. return `${item}月`
  412. })
  413. // alarmLevel
  414. // "level": 4, //级别
  415. // "name": "一般", //级别名称
  416. // "num": 43, //该级别日志数量
  417. // "ratio": 100 //该级别日志数量占比(百分比)
  418. state.pieChartLegend = [];
  419. alarmLevel && alarmLevel.map((item: any) => {
  420. state.pieChartLegend.push(item.name)
  421. state.pieChartData.push(item.ratio)
  422. })
  423. })
  424. };
  425. const getAlarmList = () => {
  426. api.iotManage.getAlarmList(state.tableData.param).then((res: any) => {
  427. state.tableData.data = res.list;
  428. state.tableData.total = res.Total;
  429. })
  430. }
  431. //打开详情页
  432. const onOpenDetailDic = (row: any) => {
  433. detailRef.value.openDialog(row);
  434. };
  435. // 打开修改产品弹窗
  436. const onOpenEditDic = (row: any) => {
  437. editDicRef.value.openDialog(row);
  438. };
  439. // 告警信息-更多信息
  440. const toMore = () => {
  441. router.push({ path: '/monitor/notice' });
  442. };
  443. // 页面加载时
  444. onMounted(() => {
  445. initEchartsResize();
  446. getOverviewData();
  447. getAlarmList();
  448. });
  449. // 由于页面缓存原因,keep-alive
  450. onActivated(() => {
  451. initEchartsResizeFun();
  452. });
  453. // 监听 vuex 中的 tagsview 开启全屏变化,重新 resize 图表,防止不出现/大小不变等
  454. watch(
  455. () => store.state.tagsViewRoutes.isTagsViewCurrenFull,
  456. () => {
  457. initEchartsResizeFun();
  458. }
  459. );
  460. // 监听 vuex 中是否开启深色主题
  461. watch(
  462. () => store.state.themeConfig.themeConfig.isIsDark,
  463. (isIsDark) => {
  464. nextTick(() => {
  465. state.charts.theme = isIsDark ? 'dark' : '';
  466. state.charts.bgColor = isIsDark ? 'transparent' : '';
  467. state.charts.color = isIsDark ? '#dadada' : '#303133';
  468. setTimeout(() => {
  469. initLineChart();
  470. }, 500);
  471. setTimeout(() => {
  472. initPieChart();
  473. }, 700);
  474. });
  475. },
  476. {
  477. deep: true,
  478. immediate: true,
  479. }
  480. );
  481. return {
  482. homeLineRef,
  483. homePieRef,
  484. homeBarRef,
  485. detailRef,
  486. editDicRef,
  487. toMore,
  488. onOpenEditDic,
  489. getAlarmList,
  490. onOpenDetailDic,
  491. getOverviewData,
  492. ...toRefs(state),
  493. };
  494. },
  495. });
  496. </script>
  497. <style scoped lang="scss">
  498. $homeNavLengh: 8;
  499. .home-container {
  500. overflow: hidden;
  501. .home-card-one,
  502. .home-card-two,
  503. .home-card-three {
  504. .icoimg {
  505. width: 50px;
  506. height: 50px;
  507. }
  508. .title_status {
  509. width: 7px;
  510. height: 7px;
  511. background: #c1bbbb;
  512. border-radius: 50px;
  513. margin-right: 5px;
  514. }
  515. .home-card-item,
  516. .home-card-top {
  517. width: 100%;
  518. height: 130px;
  519. border-radius: 4px;
  520. transition: all ease 0.3s;
  521. padding: 10px 20px;
  522. overflow: hidden;
  523. background: var(--el-color-white);
  524. color: var(--el-text-color-primary);
  525. border: 1px solid var(--next-border-color-light);
  526. &:hover {
  527. box-shadow: 0 2px 12px var(--next-color-dark-hover);
  528. transition: all ease 0.3s;
  529. }
  530. &-icon {
  531. width: 70px;
  532. height: 70px;
  533. border-radius: 100%;
  534. flex-shrink: 1;
  535. i {
  536. color: var(--el-text-color-placeholder);
  537. }
  538. }
  539. &-title {
  540. font-size: 15px;
  541. font-weight: bold;
  542. height: 30px;
  543. }
  544. }
  545. }
  546. .home-card-three {
  547. .home-card-item-title {
  548. display: flex;
  549. justify-content: space-between;
  550. // span:nth-child(2) {
  551. // color: #409eff;
  552. // }
  553. }
  554. }
  555. .home-card-one {
  556. @for $i from 0 through 3 {
  557. .home-one-animation#{$i} {
  558. opacity: 0;
  559. animation-name: error-num;
  560. animation-duration: 0.5s;
  561. animation-fill-mode: forwards;
  562. animation-delay: calc($i/10) + s;
  563. }
  564. }
  565. }
  566. .home-card-two,
  567. .home-card-three {
  568. .home-card-item {
  569. height: 300px;
  570. }
  571. .home-card-top {
  572. height: 250px;
  573. .box-card {
  574. padding: 15px 20px 20px 10px;
  575. p {
  576. margin-bottom: 10px;
  577. }
  578. &-item {
  579. margin-bottom: 10px;
  580. }
  581. }
  582. }
  583. .home-card-item,
  584. .home-card-top {
  585. width: 100%;
  586. overflow: hidden;
  587. .home-monitor {
  588. height: 100%;
  589. .flex-warp-item {
  590. width: 25%;
  591. height: 111px;
  592. display: flex;
  593. .flex-warp-item-box {
  594. margin: auto;
  595. text-align: center;
  596. color: var(--el-text-color-primary);
  597. display: flex;
  598. border-radius: 5px;
  599. background: var(--next-bg-color);
  600. cursor: pointer;
  601. transition: all 0.3s ease;
  602. &:hover {
  603. background: var(--el-color-primary-light-9);
  604. transition: all 0.3s ease;
  605. }
  606. }
  607. @for $i from 0 through $homeNavLengh {
  608. .home-animation#{$i} {
  609. opacity: 0;
  610. animation-name: error-num;
  611. animation-duration: 0.5s;
  612. animation-fill-mode: forwards;
  613. animation-delay: calc($i/10) + s;
  614. }
  615. }
  616. }
  617. }
  618. }
  619. }
  620. .text-info {
  621. color: #23c6c8;
  622. }
  623. .text-danger {
  624. color: #ed5565;
  625. }
  626. .git-res {
  627. margin-top: 20px;
  628. }
  629. .git-res .el-link {
  630. margin-right: 30px;
  631. }
  632. ul,
  633. li {
  634. padding: 0;
  635. margin: 0;
  636. list-style: none
  637. }
  638. .product {
  639. margin-top: 50px;
  640. h3 {
  641. margin-bottom: 15px;
  642. }
  643. }
  644. .product li {
  645. margin-bottom: 20px;
  646. float: left;
  647. width: 150px;
  648. }
  649. .box-card.xx {
  650. margin-top: 20px;
  651. }
  652. }</style>