dashboard.vue 18 KB

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