dashboard.vue 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705
  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="100" 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. pieChartLevel: [],
  193. pieChartData: []
  194. });
  195. // 折线图
  196. const initLineChart = () => {
  197. if (!global.dispose.some((b: any) => b === global.homeChartOne)) global.homeChartOne.dispose();
  198. global.homeChartOne = <any>echarts.init(homeLineRef.value, state.charts.theme);
  199. const option = {
  200. backgroundColor: state.charts.bgColor,
  201. title: {
  202. text: '设备消息',
  203. x: 'left',
  204. textStyle: { fontSize: '15', color: state.charts.color },
  205. },
  206. grid: { top: 70, right: 20, bottom: 30, left: 30 },
  207. tooltip: { trigger: 'axis' },
  208. legend: { data: ['消息量', '预警量'], right: 0 },
  209. xAxis: {
  210. data: state.lineChartXAxisData
  211. },
  212. yAxis: [
  213. {
  214. type: 'value',
  215. name: '条数',
  216. splitLine: { show: true, lineStyle: { type: 'dashed', color: '#f5f5f5' } },
  217. axisLabel: {
  218. margin: 2,
  219. formatter: function (value, index) {
  220. if (value >= 10000 && value < 10000000) {
  221. value = value / 10000 + "W";
  222. } else if (value >= 10000000) {
  223. value = value / 10000000 + "KW";
  224. }
  225. return value;
  226. }
  227. },
  228. },
  229. ],
  230. series: [
  231. {
  232. name: '消息量',
  233. type: 'line',
  234. symbolSize: 6,
  235. symbol: 'circle',
  236. smooth: true,
  237. data: state.lineChartMsgTotalData,
  238. lineStyle: { color: '#fe9a8b' },
  239. itemStyle: { color: '#fe9a8b', borderColor: '#fe9a8b' },
  240. areaStyle: {
  241. color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
  242. { offset: 0, color: '#fe9a8bb3' },
  243. { offset: 1, color: '#fe9a8b03' },
  244. ]),
  245. },
  246. },
  247. {
  248. name: '预警量',
  249. type: 'line',
  250. symbolSize: 6,
  251. symbol: 'circle',
  252. smooth: true,
  253. data: state.lineChartAlarmTotalData,
  254. lineStyle: { color: '#9E87FF' },
  255. itemStyle: { color: '#9E87FF', borderColor: '#9E87FF' },
  256. areaStyle: {
  257. color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
  258. { offset: 0, color: '#9E87FFb3' },
  259. { offset: 1, color: '#9E87FF03' },
  260. ]),
  261. },
  262. emphasis: {
  263. itemStyle: {
  264. color: {
  265. type: 'radial',
  266. x: 0.5,
  267. y: 0.5,
  268. r: 0.5,
  269. colorStops: [
  270. { offset: 0, color: '#9E87FF' },
  271. { offset: 0.4, color: '#9E87FF' },
  272. { offset: 0.5, color: '#fff' },
  273. { offset: 0.7, color: '#fff' },
  274. { offset: 0.8, color: '#fff' },
  275. { offset: 1, color: '#fff' },
  276. ],
  277. },
  278. borderColor: '#9E87FF',
  279. borderWidth: 2,
  280. },
  281. },
  282. },
  283. ],
  284. };
  285. (<any>global.homeChartOne).setOption(option);
  286. (<any>state.myCharts).push(global.homeChartOne);
  287. };
  288. // 饼图
  289. const initPieChart = () => {
  290. if (!global.dispose.some((b: any) => b === global.homeChartTwo)) global.homeChartTwo.dispose();
  291. global.homeChartTwo = <any>echarts.init(homePieRef.value, state.charts.theme);
  292. var getname = state.pieChartLegend;
  293. var getvalue = state.pieChartData;
  294. var data = [];
  295. for (var i = 0; i < getname.length; i++) {
  296. data.push({ name: getname[i], value: getvalue[i] });
  297. }
  298. const colorList = ['#FF0000', '#FEC279', '#968AF5', '#51A3FC', '#36C78B',];
  299. const color = state.pieChartLevel.map(level => colorList[level - 1])
  300. const option = {
  301. color,
  302. backgroundColor: state.charts.bgColor,
  303. title: {
  304. text: '预警类型',
  305. x: 'left',
  306. textStyle: { fontSize: '15', color: state.charts.color },
  307. },
  308. tooltip: { trigger: 'item', formatter: '{b} <br/> {c}%' },
  309. graphic: {
  310. elements: [
  311. {
  312. type: 'image',
  313. z: -1,
  314. left: '16.5%',
  315. top: 'center',
  316. },
  317. ],
  318. },
  319. legend: {
  320. type: 'scroll',
  321. orient: 'vertical',
  322. right: '0%',
  323. left: '65%',
  324. top: 'center',
  325. itemWidth: 14,
  326. itemHeight: 14,
  327. data: getname,
  328. textStyle: {
  329. rich: {
  330. name: {
  331. fontSize: 14,
  332. fontWeight: 400,
  333. width: 200,
  334. height: 35,
  335. padding: [0, 0, 0, 60],
  336. color: state.charts.color,
  337. },
  338. rate: {
  339. fontSize: 15,
  340. fontWeight: 500,
  341. height: 35,
  342. width: 40,
  343. padding: [0, 0, 0, 30],
  344. color: state.charts.color,
  345. },
  346. },
  347. },
  348. },
  349. series: [
  350. {
  351. type: 'pie',
  352. radius: ['70', '90'],
  353. center: ['32%', '50%'],
  354. itemStyle: {
  355. // color: function (params: any) {
  356. // console.log(params)
  357. // return colorList[params.dataIndex];
  358. // },
  359. },
  360. label: { show: false },
  361. labelLine: { show: false },
  362. data: data,
  363. },
  364. ],
  365. };
  366. (<any>global.homeChartTwo).setOption(option);
  367. (<any>state.myCharts).push(global.homeChartTwo);
  368. };
  369. // 批量设置 echarts resize
  370. const initEchartsResizeFun = () => {
  371. nextTick(() => {
  372. for (let i = 0; i < state.myCharts.length; i++) {
  373. setTimeout(() => {
  374. (<any>state.myCharts[i]).resize();
  375. }, i * 1000);
  376. }
  377. });
  378. };
  379. // 批量设置 echarts resize
  380. const initEchartsResize = () => {
  381. window.addEventListener('resize', initEchartsResizeFun);
  382. };
  383. const getOverviewData = () => {
  384. api.iotManage.getOverviewData().then((res: any) => {
  385. const { overview, device, alarmLevel } = res;
  386. // overview
  387. // "deviceTotal": 8, //设备总量
  388. // "deviceOffline": 4, //离线设备数量
  389. // "productTotal": 6, //产品总量
  390. // "productAdded": 0, //今日产品增量
  391. // "msgTotal": 107246, //设备消息总量
  392. // "msgAdded": 7391, //今日设备消息增量
  393. // "alarmTotal": 43, //设备报警总量
  394. // "alarmAdded": 0 //今日设备报警增量
  395. state.homeOne[0].allnum = overview.productTotal;
  396. state.homeOne[0].num1 = `${overview.productActivation}`;
  397. state.homeOne[0].num2 = `${overview.productDeactivation}`;
  398. state.homeOne[1].allnum = overview.deviceTotal;
  399. state.homeOne[1].num1 = `${overview.deviceTotal - overview.deviceOffline}`;
  400. state.homeOne[1].num2 = `${overview.deviceOffline}`;
  401. state.homeOne[2].allnum = overview.msgTotal;
  402. state.homeOne[2].num1 = overview.msgTotal;
  403. state.homeOne[2].num2 = `${overview.msgAdded}`;
  404. state.homeOne[3].allnum = overview.alarmTotal;
  405. state.homeOne[3].num1 = overview.alarmTotal;
  406. state.homeOne[3].num2 = `${overview.alarmAdded}`;
  407. // device
  408. // msgTotal 设备消息量月度统计
  409. // alarmTotal 设备告警量月度统计
  410. state.lineChartMsgTotalData = [];
  411. state.lineChartAlarmTotalData = [];
  412. state.lineChartXAxisData = Object.keys(device.msgTotal).map((item: any) => {
  413. state.lineChartMsgTotalData.push(device.msgTotal[item]);
  414. state.lineChartAlarmTotalData.push(device.alarmTotal[item]);
  415. return `${item}月`
  416. })
  417. // alarmLevel
  418. // "level": 4, //级别
  419. // "name": "一般", //级别名称
  420. // "num": 43, //该级别日志数量
  421. // "ratio": 100 //该级别日志数量占比(百分比)
  422. state.pieChartLegend = [];
  423. state.pieChartLevel = [];
  424. alarmLevel && alarmLevel.map((item: any) => {
  425. state.pieChartLegend.push(item.name)
  426. state.pieChartData.push(item.ratio)
  427. state.pieChartLevel.push(item.level)
  428. })
  429. })
  430. };
  431. const getAlarmList = () => {
  432. api.iotManage.getAlarmList(state.tableData.param).then((res: any) => {
  433. state.tableData.data = res.list;
  434. state.tableData.total = res.Total;
  435. })
  436. }
  437. //打开详情页
  438. const onOpenDetailDic = (row: any) => {
  439. detailRef.value.openDialog(row);
  440. };
  441. // 打开修改产品弹窗
  442. const onOpenEditDic = (row: any) => {
  443. editDicRef.value.openDialog(row);
  444. };
  445. // 告警信息-更多信息
  446. const toMore = () => {
  447. router.push({ path: '/iotmanager/alarm/log' });
  448. };
  449. // 页面加载时
  450. onMounted(() => {
  451. initEchartsResize();
  452. getOverviewData();
  453. getAlarmList();
  454. });
  455. // 由于页面缓存原因,keep-alive
  456. onActivated(() => {
  457. initEchartsResizeFun();
  458. });
  459. // 监听 vuex 中的 tagsview 开启全屏变化,重新 resize 图表,防止不出现/大小不变等
  460. watch(
  461. () => store.state.tagsViewRoutes.isTagsViewCurrenFull,
  462. () => {
  463. initEchartsResizeFun();
  464. }
  465. );
  466. // 监听 vuex 中是否开启深色主题
  467. watch(
  468. () => store.state.themeConfig.themeConfig.isIsDark,
  469. (isIsDark) => {
  470. nextTick(() => {
  471. state.charts.theme = isIsDark ? 'dark' : '';
  472. state.charts.bgColor = isIsDark ? 'transparent' : '';
  473. state.charts.color = isIsDark ? '#dadada' : '#303133';
  474. setTimeout(() => {
  475. initLineChart();
  476. }, 500);
  477. setTimeout(() => {
  478. initPieChart();
  479. }, 700);
  480. });
  481. },
  482. {
  483. deep: true,
  484. immediate: true,
  485. }
  486. );
  487. return {
  488. homeLineRef,
  489. homePieRef,
  490. homeBarRef,
  491. detailRef,
  492. editDicRef,
  493. toMore,
  494. onOpenEditDic,
  495. getAlarmList,
  496. onOpenDetailDic,
  497. getOverviewData,
  498. ...toRefs(state),
  499. };
  500. },
  501. });
  502. </script>
  503. <style scoped lang="scss">
  504. $homeNavLengh: 8;
  505. .home-container {
  506. overflow: hidden;
  507. .home-card-one,
  508. .home-card-two,
  509. .home-card-three {
  510. .icoimg {
  511. width: 50px;
  512. height: 50px;
  513. }
  514. .title_status {
  515. width: 7px;
  516. height: 7px;
  517. background: #c1bbbb;
  518. border-radius: 50px;
  519. margin-right: 5px;
  520. }
  521. .home-card-item,
  522. .home-card-top {
  523. width: 100%;
  524. height: 130px;
  525. border-radius: 4px;
  526. transition: all ease 0.3s;
  527. padding: 10px 20px;
  528. overflow: hidden;
  529. background: var(--el-color-white);
  530. color: var(--el-text-color-primary);
  531. border: 1px solid var(--next-border-color-light);
  532. &:hover {
  533. box-shadow: 0 2px 12px var(--next-color-dark-hover);
  534. transition: all ease 0.3s;
  535. }
  536. &-icon {
  537. width: 70px;
  538. height: 70px;
  539. border-radius: 100%;
  540. flex-shrink: 1;
  541. i {
  542. color: var(--el-text-color-placeholder);
  543. }
  544. }
  545. &-title {
  546. font-size: 15px;
  547. font-weight: bold;
  548. height: 30px;
  549. }
  550. }
  551. }
  552. .home-card-three {
  553. .home-card-item-title {
  554. display: flex;
  555. justify-content: space-between;
  556. // span:nth-child(2) {
  557. // color: #409eff;
  558. // }
  559. }
  560. }
  561. .home-card-one {
  562. @for $i from 0 through 3 {
  563. .home-one-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. .home-card-two,
  573. .home-card-three {
  574. .home-card-item {
  575. height: 300px;
  576. }
  577. .home-card-top {
  578. height: 250px;
  579. .box-card {
  580. padding: 15px 20px 20px 10px;
  581. p {
  582. margin-bottom: 10px;
  583. }
  584. &-item {
  585. margin-bottom: 10px;
  586. }
  587. }
  588. }
  589. .home-card-item,
  590. .home-card-top {
  591. width: 100%;
  592. overflow: hidden;
  593. .home-monitor {
  594. height: 100%;
  595. .flex-warp-item {
  596. width: 25%;
  597. height: 111px;
  598. display: flex;
  599. .flex-warp-item-box {
  600. margin: auto;
  601. text-align: center;
  602. color: var(--el-text-color-primary);
  603. display: flex;
  604. border-radius: 5px;
  605. background: var(--next-bg-color);
  606. cursor: pointer;
  607. transition: all 0.3s ease;
  608. &:hover {
  609. background: var(--el-color-primary-light-9);
  610. transition: all 0.3s ease;
  611. }
  612. }
  613. @for $i from 0 through $homeNavLengh {
  614. .home-animation#{$i} {
  615. opacity: 0;
  616. animation-name: error-num;
  617. animation-duration: 0.5s;
  618. animation-fill-mode: forwards;
  619. animation-delay: calc($i/10) + s;
  620. }
  621. }
  622. }
  623. }
  624. }
  625. }
  626. .text-info {
  627. color: #23c6c8;
  628. }
  629. .text-danger {
  630. color: #ed5565;
  631. }
  632. .git-res {
  633. margin-top: 20px;
  634. }
  635. .git-res .el-link {
  636. margin-right: 30px;
  637. }
  638. ul,
  639. li {
  640. padding: 0;
  641. margin: 0;
  642. list-style: none
  643. }
  644. .product {
  645. margin-top: 50px;
  646. h3 {
  647. margin-bottom: 15px;
  648. }
  649. }
  650. .product li {
  651. margin-bottom: 20px;
  652. float: left;
  653. width: 150px;
  654. }
  655. .box-card.xx {
  656. margin-top: 20px;
  657. }
  658. }</style>