index.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537
  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">{{ v.num3 }}</div>
  11. </div>
  12. </div>
  13. </div>
  14. </el-col>
  15. </el-row>
  16. <div class="chart-wrapper">
  17. <div class="chart-item" style="flex: 1">
  18. <div class="chart-title flex-row">
  19. 告警级别分布
  20. <el-radio-group v-model="searchType" size="small" @change="onSearchTypeChange">
  21. <el-radio-button label="">全部告警</el-radio-button>
  22. <el-radio-button label="1">已关闭告警</el-radio-button>
  23. <el-radio-button label="2">未关闭告警</el-radio-button>
  24. </el-radio-group>
  25. </div>
  26. <Chart :height="chartHeight" ref="chart1"></Chart>
  27. </div>
  28. <div class="chart-item" style="flex: 1.5">
  29. <div class="chart-title flex-row">
  30. 告警趋势
  31. <el-radio-group v-model="intervalType" size="small" @change="onIntervalTypeChange">
  32. <el-radio-button label="1">小时</el-radio-button>
  33. <el-radio-button label="2">天</el-radio-button>
  34. <el-radio-button label="3">月</el-radio-button>
  35. </el-radio-group>
  36. </div>
  37. <Chart :height="chartHeight" ref="chart2"></Chart>
  38. </div>
  39. </div>
  40. <div class="chart-wrapper">
  41. <div class="chart-item mb-0" style="flex: 1; margin-bottom: 0">
  42. <div class="chart-title flex-row">
  43. 处理状态
  44. <el-date-picker
  45. v-model="statusDateRange"
  46. type="daterange"
  47. range-separator="至"
  48. start-placeholder="开始日期"
  49. end-placeholder="结束日期"
  50. :disabled-date="disabledDate"
  51. format="YYYY-MM-DD"
  52. value-format="YYYY-MM-DD"
  53. size="small"
  54. @change="onStatusDateChange"
  55. style="max-width: 200px"
  56. />
  57. </div>
  58. <Chart :height="chartHeight" ref="chart3"></Chart>
  59. </div>
  60. <div class="chart-item" style="flex: 1.5; margin-bottom: 0">
  61. <div class="chart-title">设备告警TOP10</div>
  62. <Chart :height="chartHeight" ref="chart4"></Chart>
  63. </div>
  64. </div>
  65. </div>
  66. </template>
  67. <script lang="ts" setup>
  68. import { reactive, ref, watch, getCurrentInstance } from 'vue'
  69. import api from '/@/api/alarm'
  70. import Chart from '/@/components/chart/index.vue'
  71. import { getBarRowOption, getLineOption, getPieOption } from '/@/components/chart/options'
  72. import dayjs from 'dayjs'
  73. import { useThemeChangeFunc } from '/@/hooks/useCommon'
  74. const chartHeight = 'calc(50vh - 168px)'
  75. const chart1 = ref()
  76. const chart2 = ref()
  77. const chart3 = ref()
  78. const chart4 = ref()
  79. // 查询类型:不传为 全部告警 1已关闭告警 2未关闭告警
  80. const searchType = ref('')
  81. // 统计间隔:1小时 2天 3月
  82. const intervalType = ref('1')
  83. // 处理状态时间范围
  84. const statusDateRange = ref([dayjs().startOf('month').format('YYYY-MM-DD'), dayjs().format('YYYY-MM-DD')])
  85. // 禁用未来日期
  86. const disabledDate = (time: Date) => {
  87. return time.getTime() > Date.now()
  88. }
  89. const { proxy } = getCurrentInstance() as any
  90. const { alarm_type } = proxy.useDict('alarm_type')
  91. const alarmTypeMap: any = {}
  92. // 监听告警类型是否获取成功
  93. watch(
  94. () => alarm_type.value,
  95. (list) => {
  96. if (!list.length) return
  97. list.forEach((item: any) => {
  98. alarmTypeMap[item.value] = item.label
  99. })
  100. getOverviewData()
  101. },
  102. {
  103. immediate: true,
  104. }
  105. )
  106. const homeOne = reactive([
  107. {
  108. allnum: 0,
  109. num3: '告警总数',
  110. icoimg: 'dashboard-icon4.svg',
  111. },
  112. {
  113. allnum: 0,
  114. num3: '已关闭告警数',
  115. icoimg: 'dashboard-icon2.svg',
  116. },
  117. {
  118. allnum: 0,
  119. num3: '未关闭告警数',
  120. icoimg: 'dashboard-icon1.svg',
  121. },
  122. {
  123. allnum: 0,
  124. num3: '平均处理时长MTTR',
  125. icoimg: 'dashboard-icon3.svg',
  126. },
  127. ])
  128. const onIntervalTypeChange = () => {
  129. // 告警趋势 intervalType 统计间隔:1小时 2天 3月
  130. api.dashboard.getAnalyzeTrend({ intervalType: intervalType.value }).then((res: any) => {
  131. const resData = Object.values(res || []) as any[]
  132. const legend = Object.keys(res || []).map((item) => alarmTypeMap[item])
  133. // console.log(resData)
  134. chart2.value?.draw(
  135. getLineOption({
  136. legend,
  137. datas: resData.map((arr: any) => arr.map((item: any) => item.alarmCount)),
  138. xAxis: resData?.[0]?.map((item: any) => item.alarmDate),
  139. })
  140. )
  141. })
  142. }
  143. const onSearchTypeChange = () => {
  144. // 告警级别分布
  145. api.dashboard
  146. .getAlarmLevel({
  147. searchType: searchType.value,
  148. startDate: dayjs().subtract(20, 'year').format('YYYY-MM-DD'),
  149. endDate: dayjs().format('YYYY-MM-DD'),
  150. })
  151. .then((res: any) => {
  152. const resData = res || []
  153. const total = resData.reduce((a: any, b: any) => a + b.alarmCount, 0)
  154. chart1.value?.draw(
  155. getPieOption({
  156. radius: ['45%', '60%'],
  157. center: ['32%', '50%'],
  158. legend: {
  159. orient: 'vertical',
  160. top: 'center',
  161. left: '55%',
  162. },
  163. total,
  164. data: resData.map((item: any) => {
  165. if (total === 0) {
  166. return {
  167. name: item.levelName + ' ' + item.alarmCount,
  168. value: item.alarmCount,
  169. }
  170. }
  171. return {
  172. name: item.levelName + ' ' + item.alarmCount + ' (' + ((item.alarmCount / total) * 100).toFixed(2) + '%)',
  173. value: item.alarmCount,
  174. }
  175. }),
  176. })
  177. )
  178. })
  179. }
  180. const onStatusDateChange = () => {
  181. getAlarmStatusData()
  182. }
  183. const getAlarmStatusData = () => {
  184. // 处理状态
  185. api.dashboard
  186. .getAlarmStatus({
  187. startDate: statusDateRange.value[0],
  188. endDate: statusDateRange.value[1],
  189. })
  190. .then((res: any) => {
  191. const statusMap = {
  192. 0: '未处理',
  193. 1: '已处理',
  194. 2: '已忽略',
  195. }
  196. const resData = res || []
  197. const total = resData.reduce((a: any, b: any) => a + b.alarmCount, 0)
  198. chart3.value?.draw(
  199. getPieOption({
  200. total,
  201. radius: ['45%', '60%'],
  202. center: ['32%', '50%'],
  203. legend: {
  204. orient: 'vertical',
  205. top: 'center',
  206. left: '55%',
  207. },
  208. centerText: '总计',
  209. data: resData.map((item: any) => {
  210. const name = statusMap[item.status as keyof typeof statusMap]
  211. if (total == 0) {
  212. return { name: name + ' ' + item.alarmCount, value: item.alarmCount }
  213. }
  214. return { name: name + ' ' + item.alarmCount + ' (' + ((item.alarmCount / total) * 100).toFixed(2) + '%)', value: item.alarmCount }
  215. }),
  216. })
  217. )
  218. })
  219. }
  220. const getOverviewData = () => {
  221. // 顶部统计数据
  222. api.dashboard.getTotalAlarmStatistics().then((res: any) => {
  223. homeOne[0].allnum = res.alarmTotalCount
  224. homeOne[1].allnum = res.closeAlarmCount
  225. homeOne[2].allnum = res.unCloseAlarmCount
  226. homeOne[3].allnum = res.averageDealTime
  227. })
  228. onSearchTypeChange()
  229. onIntervalTypeChange()
  230. // 获取处理状态数据
  231. getAlarmStatusData()
  232. api.dashboard.getDeviceAlarmTop10().then((res: any) => {
  233. const list = res || []
  234. const chartData = getBarRowOption({
  235. data: list.map((item: any) => item.alarmCount),
  236. xAxis: list.map((item: any) => item.deviceName),
  237. // data: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
  238. // xAxis: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10'],
  239. })
  240. chart4.value.draw(chartData)
  241. })
  242. }
  243. useThemeChangeFunc(getOverviewData)
  244. </script>
  245. <style scoped lang="scss">
  246. $homeNavLengh: 8;
  247. .chart-wrapper {
  248. display: flex;
  249. justify-content: space-between;
  250. align-items: stretch;
  251. gap: 16px;
  252. .chart-item {
  253. background-color: var(--el-color-white);
  254. padding: 12px 15px;
  255. border-radius: 8px;
  256. margin-bottom: 16px;
  257. flex: 1;
  258. min-width: 200px;
  259. }
  260. .chart-title {
  261. font-size: 15px;
  262. font-weight: bold;
  263. padding-left: 5px;
  264. }
  265. }
  266. .home-card-top-part {
  267. background-color: var(--el-color-white);
  268. border-radius: 8px;
  269. padding: 20px 20px;
  270. .top {
  271. display: flex;
  272. justify-content: space-around;
  273. overflow: hidden;
  274. align-items: center;
  275. }
  276. .icoimg {
  277. width: 54px !important;
  278. height: 54px !important;
  279. margin-right: 12px;
  280. }
  281. .label {
  282. font-size: 14px;
  283. font-weight: 500;
  284. }
  285. .divider {
  286. border-top: 1px solid var(--el-border-color-light);
  287. margin: 12px 0 15px;
  288. }
  289. .card-right {
  290. flex: 1;
  291. display: flex;
  292. flex-direction: column;
  293. justify-content: space-between;
  294. white-space: nowrap;
  295. line-height: 1;
  296. height: 54px;
  297. .font30 {
  298. color: #4285f4;
  299. font-weight: bold;
  300. font-size: 30px;
  301. }
  302. }
  303. .card-bottom {
  304. font-size: 12px;
  305. display: flex;
  306. align-items: center;
  307. justify-content: space-around;
  308. gap: 12px;
  309. white-space: nowrap;
  310. .split {
  311. border-right: 1px solid var(--el-border-color-light);
  312. height: 20px;
  313. }
  314. .icon {
  315. width: 17px;
  316. height: 17px;
  317. }
  318. .info {
  319. font-size: 12px;
  320. font-weight: 500;
  321. }
  322. }
  323. }
  324. .home-container {
  325. overflow: hidden;
  326. .home-card-one,
  327. .home-card-two,
  328. .home-card-three {
  329. .icoimg {
  330. width: 75px;
  331. height: 75px;
  332. }
  333. .title_status {
  334. width: 7px;
  335. height: 7px;
  336. background: #c1bbbb;
  337. border-radius: 50px;
  338. margin-right: 5px;
  339. }
  340. .home-card-item,
  341. .home-card-top {
  342. width: 100%;
  343. border-radius: 8px;
  344. transition: all ease 0.3s;
  345. padding: 10px 20px;
  346. overflow: hidden;
  347. background: var(--el-color-white);
  348. color: var(--el-text-color-primary);
  349. // border: 1px solid var(--next-border-color-light);
  350. &:hover {
  351. // box-shadow: 0 2px 12px var(--next-color-dark-hover);
  352. transition: all ease 0.3s;
  353. }
  354. &-icon {
  355. width: 70px;
  356. height: 70px;
  357. border-radius: 100%;
  358. flex-shrink: 1;
  359. i {
  360. color: var(--el-text-color-placeholder);
  361. }
  362. }
  363. &-title {
  364. font-size: 15px;
  365. font-weight: bold;
  366. height: 30px;
  367. }
  368. }
  369. }
  370. .home-card-three {
  371. .home-card-item-title {
  372. display: flex;
  373. justify-content: space-between;
  374. // span:nth-child(2) {
  375. // color: #409eff;
  376. // }
  377. }
  378. }
  379. .home-card-one {
  380. @for $i from 0 through 3 {
  381. .home-one-animation#{$i} {
  382. opacity: 0;
  383. animation-name: error-num;
  384. animation-duration: 0.5s;
  385. animation-fill-mode: forwards;
  386. animation-delay: calc($i/10) + s;
  387. }
  388. }
  389. }
  390. .home-card-two,
  391. .home-card-three {
  392. .home-card-top {
  393. height: 250px;
  394. .box-card {
  395. padding: 15px 20px 20px 10px;
  396. p {
  397. margin-bottom: 10px;
  398. }
  399. &-item {
  400. margin-bottom: 10px;
  401. }
  402. }
  403. }
  404. .home-card-item,
  405. .home-card-top {
  406. width: 100%;
  407. overflow: hidden;
  408. .home-monitor {
  409. height: 100%;
  410. .flex-warp-item {
  411. width: 25%;
  412. height: 111px;
  413. display: flex;
  414. .flex-warp-item-box {
  415. margin: auto;
  416. text-align: center;
  417. color: var(--el-text-color-primary);
  418. display: flex;
  419. border-radius: 5px;
  420. background: var(--next-bg-color);
  421. cursor: pointer;
  422. transition: all 0.3s ease;
  423. &:hover {
  424. background: var(--el-color-primary-light-9);
  425. transition: all 0.3s ease;
  426. }
  427. }
  428. @for $i from 0 through $homeNavLengh {
  429. .home-animation#{$i} {
  430. opacity: 0;
  431. animation-name: error-num;
  432. animation-duration: 0.5s;
  433. animation-fill-mode: forwards;
  434. animation-delay: calc($i/10) + s;
  435. }
  436. }
  437. }
  438. }
  439. }
  440. }
  441. .text-info {
  442. color: #23c6c8;
  443. }
  444. .text-danger {
  445. color: #ed5565;
  446. }
  447. .git-res {
  448. margin-top: 20px;
  449. }
  450. .git-res .el-link {
  451. margin-right: 30px;
  452. }
  453. ul,
  454. li {
  455. padding: 0;
  456. margin: 0;
  457. list-style: none;
  458. }
  459. .product {
  460. margin-top: 50px;
  461. h3 {
  462. margin-bottom: 15px;
  463. }
  464. }
  465. .product li {
  466. margin-bottom: 20px;
  467. float: left;
  468. width: 150px;
  469. }
  470. .box-card.xx {
  471. margin-top: 20px;
  472. }
  473. }
  474. .home-card-item.chart {
  475. padding: 10px !important;
  476. }
  477. </style>