copy.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447
  1. <template>
  2. <div class="page">
  3. <div class="flex-row" style="gap: 12px">
  4. <el-card shadow="nover">
  5. <div class="title flex-row">
  6. <div class="">
  7. 今日告警 <el-tag size="small">{{ todayCount.currentDayCount }}</el-tag>
  8. </div>
  9. <div>
  10. 本月告警 <el-tag size="small">{{ todayCount.currentMonthCount }}</el-tag>
  11. </div>
  12. </div>
  13. <VueUiXy v-if="dataset?.length" :config="config" :dataset="dataset" />
  14. <VueUiSkeleton v-else :config="{ type: 'bar' }" />
  15. </el-card>
  16. <el-card shadow="nover">
  17. <div class="title">告警设备top10</div>
  18. <VueUiXy v-if="dataset2?.length" :config="config2" :dataset="dataset2" />
  19. <VueUiSkeleton v-else :config="{ type: 'bar' }" />
  20. </el-card>
  21. <el-card shadow="nover">
  22. <div class="title">最新告警</div>
  23. <el-table class="flex1" :data="alarmNewList" border style="width: 100%" size="small">
  24. <el-table-column prop="createdAt" label="告警日期" width="140" align="center" />
  25. <el-table-column prop="status" :formatter="(row:any) => (row.status ? '已处理' : '未处理')" label="告警状态" width="70" align="center" />
  26. <el-table-column prop="level" label="告警等级" :formatter="(row:any) => typeFormat(row.level)" width="100" align="center" />
  27. <el-table-column prop="ruleName" label="告警说明" show-overflow-tooltip aign="center" />
  28. </el-table>
  29. </el-card>
  30. </div>
  31. <div class="flex-row" style="gap: 12px">
  32. <el-card shadow="nover">
  33. <div class="title">
  34. 告警统计
  35. <el-date-picker
  36. v-model="dateRange"
  37. type="daterange"
  38. range-separator="至"
  39. start-placeholder="开始日期"
  40. end-placeholder="结束日期"
  41. @change="getAlarmStatistics"
  42. format="YYYY-MM-DD"
  43. value-format="YYYY-MM-DD"
  44. size="small"
  45. style="max-width: 200px"
  46. :clearable="false"
  47. :disabledDate="disabledDate"
  48. />
  49. </div>
  50. <VueUiXy v-if="dataset4?.length" :config="config4" :dataset="dataset4" />
  51. <VueUiSkeleton v-else :config="{ type: 'bar' }" />
  52. </el-card>
  53. <el-card shadow="nover">
  54. <div class="title">告警增长趋势</div>
  55. <VueUiXy v-if="dataset5?.length" :config="config5" :dataset="dataset5" />
  56. <VueUiSkeleton v-else :config="{ type: 'donut' }" />
  57. </el-card>
  58. <el-card shadow="nover">
  59. <div class="title">
  60. 部门告警分析
  61. <el-date-picker
  62. v-model="deptAlarmDate"
  63. type="daterange"
  64. range-separator="至"
  65. start-placeholder="开始日期"
  66. end-placeholder="结束日期"
  67. @change="getDeptAlarm"
  68. format="YYYY-MM-DD"
  69. value-format="YYYY-MM-DD"
  70. size="small"
  71. style="max-width: 200px"
  72. :clearable="false"
  73. :disabledDate="disabledDate"
  74. />
  75. </div>
  76. <VueUiXy v-if="dataset6?.length" :config="config6" :dataset="dataset6" />
  77. <VueUiSkeleton v-else :config="{ type: 'bar' }" />
  78. </el-card>
  79. </div>
  80. <div class="flex-row" style="gap: 12px">
  81. <el-card shadow="nover">
  82. <div class="title">
  83. 告警状态
  84. <el-date-picker
  85. v-model="alarmStatusDate"
  86. type="daterange"
  87. range-separator="至"
  88. start-placeholder="开始日期"
  89. end-placeholder="结束日期"
  90. @change="getAlarmStatus"
  91. format="YYYY-MM-DD"
  92. value-format="YYYY-MM-DD"
  93. size="small"
  94. style="max-width: 200px"
  95. :clearable="false"
  96. :disabledDate="disabledDate"
  97. />
  98. </div>
  99. <VueUiDonut v-if="dataset7?.length" :config="config7" :dataset="dataset7" />
  100. <VueUiSkeleton v-else :config="{ type: 'donut' }" />
  101. </el-card>
  102. <el-card shadow="nover">
  103. <div class="title">
  104. 告警等级
  105. <el-date-picker
  106. v-model="alarmLevelDate"
  107. type="daterange"
  108. range-separator="至"
  109. start-placeholder="开始日期"
  110. end-placeholder="结束日期"
  111. format="YYYY-MM-DD"
  112. value-format="YYYY-MM-DD"
  113. @change="getAlarmLevel"
  114. size="small"
  115. style="max-width: 200px"
  116. :clearable="false"
  117. :disabledDate="disabledDate"
  118. />
  119. </div>
  120. <VueUiXy v-if="dataset9?.length" :config="config9" :dataset="dataset9" />
  121. <VueUiSkeleton v-else :config="{ type: 'bar' }" />
  122. </el-card>
  123. <el-card shadow="nover">
  124. <div class="title">
  125. 告警类型
  126. <el-date-picker
  127. v-model="alarmTypeDate"
  128. type="daterange"
  129. range-separator="至"
  130. start-placeholder="开始日期"
  131. end-placeholder="结束日期"
  132. format="YYYY-MM-DD"
  133. value-format="YYYY-MM-DD"
  134. @change="getAlarmType"
  135. size="small"
  136. style="max-width: 200px"
  137. :clearable="false"
  138. :disabledDate="disabledDate"
  139. />
  140. </div>
  141. <VueUiDonut v-if="dataset8?.length" :config="config8" :dataset="dataset8" />
  142. <VueUiSkeleton v-else :config="{ type: 'donut' }" />
  143. </el-card>
  144. </div>
  145. </div>
  146. </template>
  147. <script lang="ts" setup>
  148. import { unref, reactive, getCurrentInstance, ref } from 'vue'
  149. import { VueUiXy, VueUiDonut, VueUiSkeleton } from 'vue-data-ui'
  150. import 'vue-data-ui/style.css'
  151. import { getBarData, getPieSmallData } from '/@/utils/dataUiOptions'
  152. import api from '/@/api/alarm'
  153. import { useThemeChange } from '/@/hooks/useCommon'
  154. import dayjs from 'dayjs'
  155. const { proxy } = getCurrentInstance() as any
  156. const { alarm_type } = proxy.useDict('alarm_type')
  157. const disabledDate = (time: any) => time.getTime() > Date.now()
  158. function typeFormat(type: string) {
  159. return proxy.selectDictLabel(unref(alarm_type), type)
  160. }
  161. const config = ref<any>({})
  162. const config2 = ref<any>({})
  163. const config4 = ref<any>({})
  164. const config5 = ref<any>({})
  165. const config6 = ref<any>({})
  166. const config7 = ref<any>({})
  167. const config8 = ref<any>({})
  168. const config9 = ref<any>({})
  169. const dataset = ref<any[]>([])
  170. const dataset2 = ref<any[]>([])
  171. const dataset4 = ref<any[]>([])
  172. const dataset5 = ref<any[]>([])
  173. const dataset6 = ref<any[]>([])
  174. const dataset7 = ref<any[]>([])
  175. const dataset8 = ref<any[]>([])
  176. const dataset9 = ref<any[]>([])
  177. // 监听暗黑模式变化,将 vue-data-ui 的 config 传进来,就能自动更新主题
  178. useThemeChange([config, config2, config4, config5, config6, config7, config8, config9])
  179. const todayCount = reactive({
  180. currentDayCount: '-',
  181. currentMonthCount: '-',
  182. })
  183. const alarmNewList = ref<any[]>([])
  184. const defaultDateRange = [dayjs().subtract(7, 'day').format('YYYY-MM-DD'), dayjs().format('YYYY-MM-DD')]
  185. const dateRange = ref([...defaultDateRange])
  186. const analyzeTrendDate = ref(dayjs().format('YYYY-MM'))
  187. const deptAlarmDate = ref([...defaultDateRange])
  188. const alarmStatusDate = ref([...defaultDateRange])
  189. const alarmTypeDate = ref([...defaultDateRange])
  190. const alarmLevelDate = ref([...defaultDateRange])
  191. getData()
  192. function getData() {
  193. // 今日告警信息
  194. api.dashboard.getCurrentDayInfo().then((res: any) => {
  195. const list = res?.data.alarmList || []
  196. todayCount.currentDayCount = res?.data?.currentDayCount
  197. todayCount.currentMonthCount = res?.data?.currentMonthCount
  198. const chartData = getBarData({
  199. xAxis: list.map((item: any) => item.alarmDate),
  200. legend: ['今日告警'],
  201. datas: [list.map((item: any) => item.alarmCount)],
  202. width: 300,
  203. height: 300,
  204. colors: ['#4B79F2'],
  205. modulo: 3,
  206. responsive: true,
  207. })
  208. config.value = chartData.config
  209. dataset.value = chartData.dataset
  210. })
  211. // 最新告警
  212. api.dashboard.getAlarmNewList().then((res: any) => {
  213. alarmNewList.value = res || []
  214. })
  215. // 告警统计
  216. getAlarmStatistics()
  217. // 告警趋势统计
  218. getAnalyzeTrend()
  219. // 部门告警统计
  220. getDeptAlarm()
  221. // 告警状态统计
  222. getAlarmStatus()
  223. // 告警类型统计
  224. getAlarmType()
  225. // 告警等级统计
  226. getAlarmLevel()
  227. // 告警设备top10
  228. api.dashboard.getDeviceAlarmTop10().then((res: any) => {
  229. const list = res || []
  230. const chartData = getBarData({
  231. xAxis: list.map((item: any) => item.deviceName),
  232. legend: ['告警设备top10'],
  233. datas: [list.map((item: any) => item.alarmCount)],
  234. colors: ['#FF7D5C'],
  235. responsive: true,
  236. })
  237. config2.value = chartData.config
  238. dataset2.value = chartData.dataset
  239. })
  240. }
  241. function getAlarmStatistics() {
  242. api.dashboard
  243. .getAlarmStatistics({
  244. startDate: dateRange.value[0],
  245. endDate: dateRange.value[1],
  246. })
  247. .then((res: any) => {
  248. const list = res || []
  249. const chartData = getBarData({
  250. xAxis: list.map((item: any) => item.alarmDate),
  251. legend: ['告警统计'],
  252. datas: [list.map((item: any) => item.alarmCount || 0)],
  253. modulo: 3,
  254. colors: ['#5AD8A6'],
  255. responsive: true,
  256. })
  257. config4.value = chartData.config
  258. dataset4.value = chartData.dataset
  259. })
  260. }
  261. function getAnalyzeTrend() {
  262. api.dashboard
  263. .getAnalyzeTrend({
  264. searchMonth: analyzeTrendDate.value,
  265. })
  266. .then((res: any) => {
  267. const list = res || []
  268. const chartData = getBarData({
  269. xAxis: list.map((item: any) => item.month),
  270. legend: ['部门告警'],
  271. datas: [list.map((item: any) => item.alarmCount)],
  272. modulo: 5,
  273. colors: ['#FFB64D'],
  274. responsive: true,
  275. })
  276. config5.value = chartData.config
  277. dataset5.value = chartData.dataset
  278. })
  279. }
  280. function getDeptAlarm() {
  281. api.dashboard
  282. .getDeptAlarm({
  283. startDate: deptAlarmDate.value[0],
  284. endDate: deptAlarmDate.value[1],
  285. })
  286. .then((res: any) => {
  287. const list = res || []
  288. const chartData = getBarData({
  289. xAxis: list.map((item: any) => item.deptName),
  290. legend: ['部门告警'],
  291. datas: [list.map((item: any) => item.alarmCount)],
  292. modulo: 5,
  293. colors: ['#5B8FF9'],
  294. responsive: true,
  295. })
  296. config6.value = chartData.config
  297. dataset6.value = chartData.dataset
  298. })
  299. }
  300. function getAlarmStatus() {
  301. api.dashboard
  302. .getAlarmStatus({
  303. startDate: alarmStatusDate.value[0],
  304. endDate: alarmStatusDate.value[1],
  305. })
  306. .then((res: any) => {
  307. const list = res || []
  308. const chartData = getPieSmallData({
  309. legend: list.map((item: any) => (item.status === '1' ? '已处理' : '未处理')),
  310. datas: list.map((item: any) => [item.alarmCount]),
  311. colors: ['#5AD8A6', '#E86452'],
  312. responsive: true,
  313. })
  314. config7.value = chartData.config
  315. dataset7.value = chartData.dataset
  316. })
  317. }
  318. function getAlarmType() {
  319. api.dashboard
  320. .getAlarmType({
  321. startDate: alarmTypeDate.value[0],
  322. endDate: alarmTypeDate.value[1],
  323. })
  324. .then((res: any) => {
  325. const list = res || []
  326. const chartData = getPieSmallData({
  327. legend: list.map((item: any) => (item.alarmType === '1' ? '规则告警' : '自助上报告警')),
  328. datas: list.map((item: any) => [item.alarmCount]),
  329. colors: ['#7453E5', '#FFB64D'],
  330. responsive: true,
  331. })
  332. config8.value = chartData.config
  333. dataset8.value = chartData.dataset
  334. })
  335. }
  336. function getAlarmLevel() {
  337. api.dashboard
  338. .getAlarmLevel({
  339. startDate: alarmLevelDate.value[0],
  340. endDate: alarmLevelDate.value[1],
  341. })
  342. .then((res: any) => {
  343. const list = res || []
  344. const chartData = getBarData({
  345. xAxis: list.map((item: any) => item.levelName),
  346. legend: ['告警等级'],
  347. datas: [list.map((item: any) => item.alarmCount)],
  348. modulo: 3,
  349. colors: ['#269A99'],
  350. responsive: true,
  351. })
  352. config9.value = chartData.config
  353. dataset9.value = chartData.dataset
  354. })
  355. }
  356. </script>
  357. <style scoped lang="scss">
  358. .page {
  359. display: flex;
  360. flex-direction: column;
  361. justify-content: space-between;
  362. gap: 12px;
  363. .vue-ui-skeleton {
  364. height: 100%;
  365. & ::v-deep > svg {
  366. height: 100%;
  367. }
  368. }
  369. .title {
  370. font-size: 14px;
  371. color: #333;
  372. font-weight: 500;
  373. line-height: 1;
  374. height: 24px;
  375. min-height: 24px;
  376. margin-bottom: 6px;
  377. display: flex;
  378. align-items: center;
  379. justify-content: space-between;
  380. }
  381. .flex-row {
  382. flex: 1;
  383. .el-card {
  384. height: 100%;
  385. flex: 1;
  386. // overflow: hidden;
  387. box-sizing: border-box;
  388. & ::v-deep .el-card__body {
  389. padding: 1.5vh 1vw;
  390. // gap: 10px;
  391. height: 100%;
  392. display: flex;
  393. flex-direction: column;
  394. justify-content: space-between;
  395. // overflow: hidden;
  396. .vue-ui-xy,
  397. .vue-ui-donut,
  398. .vue-ui-skeleton {
  399. // flex: 1 !important;
  400. height: calc(100% - 30px) !important;
  401. // overflow: auto;
  402. // display: flex;
  403. // flex-direction: column;
  404. // justify-content: center;
  405. // align-items: center;
  406. &-svg {
  407. height: 100%;
  408. }
  409. .vue-data-ui-fulscreen--off {
  410. height: 100%;
  411. }
  412. }
  413. .vue-ui-skeleton {
  414. overflow: hidden;
  415. }
  416. }
  417. }
  418. }
  419. }
  420. </style>