device.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419
  1. <template>
  2. <el-container>
  3. <el-header>
  4. <el-card shadow="hover" style="margin-top: 10px">
  5. <el-row justify="end" align="middle">
  6. <el-col :span="19" style="text-align: right">
  7. <el-radio-group v-model="timeRadio" size="small" @change="handleDateRangeChange">
  8. <!-- <el-radio-button label="今日" value="today" />-->
  9. <el-radio-button label="近7天" value="week" />
  10. <el-radio-button label="近30天" value="month" />
  11. </el-radio-group>
  12. </el-col>
  13. <el-col :span="5" style="text-align: right">
  14. <el-date-picker
  15. v-model="dateRange"
  16. type="daterange"
  17. range-separator="-"
  18. start-placeholder="开始日期"
  19. end-placeholder="结束日期"
  20. style="margin-left: 10px; margin-right: 30px"
  21. />
  22. </el-col>
  23. </el-row>
  24. </el-card>
  25. </el-header>
  26. <el-main style="margin-top: 20px">
  27. <el-row :gutter="20">
  28. <el-col :span="6" v-for="(item, index) in stats" :key="index">
  29. <el-card shadow="hover" class="stat-card">
  30. <h3>{{ item.label }}</h3>
  31. <p :class="['number', item.class]">{{ item.value }}</p>
  32. </el-card>
  33. </el-col>
  34. </el-row>
  35. <el-row :gutter="20" style="margin-top: 20px">
  36. <el-col :span="18">
  37. <el-row :gutter="20">
  38. <el-col :span="8">
  39. <el-card shadow="hover">
  40. <h3>类型占比</h3>
  41. <div ref="typePie" class="chart-placeholder"></div>
  42. </el-card>
  43. </el-col>
  44. <el-col :span="8">
  45. <el-card shadow="hover">
  46. <h3>告警级别</h3>
  47. <div ref="alarmLevel" class="chart-placeholder"></div>
  48. </el-card>
  49. </el-col>
  50. <el-col :span="8">
  51. <el-card shadow="hover">
  52. <h3>告警问题统计</h3>
  53. <div ref="alarmCount" class="chart-placeholder"></div>
  54. </el-card>
  55. </el-col>
  56. </el-row>
  57. <el-row :gutter="20" style="margin-top: 20px">
  58. <el-col :span="12">
  59. <el-card shadow="hover">
  60. <h3>在线时长排行</h3>
  61. <div ref="onlineTime" class="chart-placeholder"></div>
  62. </el-card>
  63. </el-col>
  64. <el-col :span="12">
  65. <el-card shadow="hover">
  66. <h3>告警数量排行</h3>
  67. <div ref="alarmNum" class="chart-placeholder"></div>
  68. </el-card>
  69. </el-col>
  70. </el-row>
  71. </el-col>
  72. <el-col :span="6">
  73. <el-card shadow="hover">
  74. <h3>告警清单</h3>
  75. <el-table v-loading="loading" :data="alarmList" row-key="id">
  76. <el-table-column label="设备名称" align="left" prop="deviceName" width="100" :show-overflow-tooltip="true" />
  77. <el-table-column label="告警等级" align="center" prop="errorLevel" width="80">
  78. <template #default="scope">
  79. <dict-tag :options="smsb_device_error_level" :value="scope.row.errorLevel" />
  80. </template>
  81. </el-table-column>
  82. <el-table-column label="告警类型" align="center" prop="errorType" width="80">
  83. <template #default="scope">
  84. <dict-tag :options="smsb_device_error_type" :value="scope.row.errorType" />
  85. </template>
  86. </el-table-column>
  87. <el-table-column label="创建时间" align="left" prop="createTime" width="160" />
  88. </el-table>
  89. </el-card>
  90. </el-col>
  91. </el-row>
  92. </el-main>
  93. </el-container>
  94. </template>
  95. <script setup lang="ts">
  96. import { reactive } from 'vue';
  97. import { deviceStatistics } from '@/api/smsb/device/device';
  98. import * as echarts from 'echarts';
  99. import { DeviceErrorRecordQuery, DeviceErrorRecordVO } from '@/api/smsb/device/errorRecord_type';
  100. import { alarmCountByLevel, alarmCountByType, alarmNumTop, listDeviceErrorRecord, onlineTimeTop } from '@/api/smsb/device/errorRecord';
  101. const alarmList = ref<DeviceErrorRecordVO[]>([]);
  102. const { proxy } = getCurrentInstance() as ComponentInternalInstance;
  103. const { smsb_device_error_level, smsb_device_error_type } = toRefs<any>(proxy?.useDict('smsb_device_error_level', 'smsb_device_error_type'));
  104. const total = ref(0);
  105. const loading = ref(true);
  106. const timeRadio = ref('week');
  107. const dateRange = ref(['2025-01-01', '2025-01-01']);
  108. const typePie = ref();
  109. const alarmLevel = ref();
  110. const alarmCount = ref();
  111. const alarmNum = ref();
  112. const onlineTime = ref();
  113. const dialogData = reactive<DialogPageData<DeviceErrorRecordQuery>>({
  114. dialogQueryParams: {
  115. pageNum: 1,
  116. pageSize: 18,
  117. deviceId: undefined,
  118. params: {}
  119. }
  120. });
  121. const { dialogQueryParams } = toRefs(dialogData);
  122. const stats = reactive([
  123. { label: '设备总量', value: 0, class: '' },
  124. { label: '已上线设备', value: 0, class: 'success' },
  125. { label: '离线设备', value: 0, class: 'danger' },
  126. { label: '待接入设备', value: 0, class: 'warning' }
  127. ]);
  128. const handleDateRangeChange = () => {
  129. const rangeType = timeRadio.value;
  130. const today = new Date();
  131. const startDate = new Date();
  132. const endDate = new Date();
  133. switch (rangeType) {
  134. case 'today':
  135. break;
  136. case 'week':
  137. startDate.setDate(today.getDate() - 7);
  138. break;
  139. case 'month':
  140. startDate.setMonth(today.getMonth() - 1);
  141. break;
  142. default:
  143. throw new Error('Invalid range type');
  144. }
  145. dateRange.value = [formatDate(startDate), formatDate(endDate)];
  146. alarmCountData();
  147. getAlarmLevel();
  148. getAlarmNumTop();
  149. getOnlineTimeTop();
  150. };
  151. const formatDate = (date: Date) => {
  152. const year = date.getFullYear();
  153. const month = String(date.getMonth() + 1).padStart(2, '0');
  154. const day = String(date.getDate()).padStart(2, '0');
  155. return `${year}-${month}-${day}`;
  156. };
  157. const getOnlineTimeTop = async () => {
  158. const params = {
  159. startTime: dateRange.value[0],
  160. endTime: dateRange.value[1]
  161. };
  162. const res = await onlineTimeTop(params);
  163. const onlineTimeTopInstance = echarts.init(onlineTime.value, 'macaroons');
  164. onlineTimeTopInstance.setOption({
  165. title: {
  166. text: ''
  167. },
  168. tooltip: {
  169. trigger: 'axis',
  170. axisPointer: {
  171. type: 'shadow'
  172. }
  173. },
  174. legend: {},
  175. grid: {
  176. left: '3%',
  177. right: '4%',
  178. bottom: '3%',
  179. containLabel: true
  180. },
  181. xAxis: {
  182. type: 'value',
  183. boundaryGap: [0, 0.01]
  184. },
  185. yAxis: {
  186. type: 'category',
  187. data: res.data.deviceNameList
  188. },
  189. series: [
  190. {
  191. name: '',
  192. type: 'bar',
  193. data: res.data.onlineTimeList
  194. }
  195. ]
  196. });
  197. };
  198. const getAlarmNumTop = async () => {
  199. const params = {
  200. startTime: dateRange.value[0],
  201. endTime: dateRange.value[1]
  202. };
  203. const res = await alarmNumTop(params);
  204. const alarmNumTopInstance = echarts.init(alarmNum.value, 'macaroons');
  205. alarmNumTopInstance.setOption({
  206. title: {
  207. text: ''
  208. },
  209. tooltip: {
  210. trigger: 'axis',
  211. axisPointer: {
  212. type: 'shadow'
  213. }
  214. },
  215. legend: {},
  216. grid: {
  217. left: '3%',
  218. right: '4%',
  219. bottom: '3%',
  220. containLabel: true
  221. },
  222. xAxis: {
  223. type: 'value',
  224. boundaryGap: [0, 0.01]
  225. },
  226. yAxis: {
  227. type: 'category',
  228. data: res.data.deviceNameList
  229. },
  230. series: [
  231. {
  232. name: '',
  233. type: 'bar',
  234. data: res.data.alarmNumList
  235. }
  236. ]
  237. });
  238. };
  239. const alarmCountData = async () => {
  240. const params = {
  241. startTime: dateRange.value[0],
  242. endTime: dateRange.value[1]
  243. };
  244. const res = await alarmCountByType(params);
  245. const alarmTypeInstance = echarts.init(alarmCount.value, 'macaroons');
  246. alarmTypeInstance.setOption({
  247. xAxis: {
  248. type: 'category',
  249. data: res.data.alarmType
  250. },
  251. yAxis: {
  252. type: 'value'
  253. },
  254. series: [
  255. {
  256. data: res.data.alarmCount,
  257. type: 'bar'
  258. }
  259. ]
  260. });
  261. };
  262. const getAlarmLevel = async () => {
  263. const params = {
  264. startTime: dateRange.value[0],
  265. endTime: dateRange.value[1]
  266. };
  267. const res = await alarmCountByLevel(params);
  268. const alarmLevelInstance = echarts.init(alarmLevel.value, 'macaroons');
  269. alarmLevelInstance.setOption({
  270. title: {
  271. text: ''
  272. },
  273. tooltip: {
  274. trigger: 'axis'
  275. },
  276. legend: {
  277. data: ['普通', '紧急']
  278. },
  279. grid: {
  280. left: '3%',
  281. right: '4%',
  282. bottom: '3%',
  283. containLabel: true
  284. },
  285. toolbox: {
  286. feature: {
  287. saveAsImage: {}
  288. }
  289. },
  290. xAxis: {
  291. type: 'category',
  292. boundaryGap: false,
  293. data: res.data.alarmDateList
  294. },
  295. yAxis: {
  296. type: 'value'
  297. },
  298. series: [
  299. {
  300. name: '普通',
  301. type: 'line',
  302. stack: 'Total',
  303. data: res.data.normalAlamList
  304. },
  305. {
  306. name: '紧急',
  307. type: 'line',
  308. stack: 'Total',
  309. data: res.data.dangerAlamList
  310. }
  311. ]
  312. });
  313. };
  314. const getDeviceStatistics = async () => {
  315. const res = await deviceStatistics();
  316. stats.forEach((item) => {
  317. switch (item.label) {
  318. case '设备总量':
  319. item.value = res.data.totalNum;
  320. break;
  321. case '已上线设备':
  322. item.value = res.data.onlineNum;
  323. break;
  324. case '离线设备':
  325. item.value = res.data.offlineNum;
  326. break;
  327. case '待接入设备':
  328. item.value = res.data.initNum;
  329. break;
  330. }
  331. });
  332. const typePieInstance = echarts.init(typePie.value, 'macaroons');
  333. typePieInstance.setOption({
  334. title: {
  335. text: '',
  336. subtext: '',
  337. left: 'center'
  338. },
  339. tooltip: {
  340. trigger: 'item'
  341. },
  342. legend: {
  343. orient: 'vertical',
  344. left: 'left'
  345. },
  346. series: [
  347. {
  348. name: '类型占比',
  349. type: 'pie',
  350. radius: '65%',
  351. data: [
  352. { value: (res.data.onlineNum / res.data.totalNum) * 100, name: '在线' },
  353. { value: (res.data.offlineNum / res.data.totalNum) * 100, name: '离线' },
  354. { value: (res.data.initNum / res.data.totalNum) * 100, name: '待接入' }
  355. ],
  356. emphasis: {
  357. itemStyle: {
  358. shadowBlur: 10,
  359. shadowOffsetX: 0,
  360. shadowColor: 'rgba(0, 0, 0, 0.5)'
  361. }
  362. }
  363. }
  364. ]
  365. });
  366. };
  367. const getAlarmList = async () => {
  368. loading.value = true;
  369. const res = await listDeviceErrorRecord(dialogQueryParams.value);
  370. alarmList.value = res.rows;
  371. total.value = res.total;
  372. loading.value = false;
  373. };
  374. onMounted(() => {
  375. handleDateRangeChange();
  376. getDeviceStatistics();
  377. getAlarmList();
  378. alarmCountData();
  379. getAlarmLevel();
  380. getAlarmNumTop();
  381. getOnlineTimeTop();
  382. });
  383. </script>
  384. <style scoped>
  385. .stat-card {
  386. text-align: center;
  387. }
  388. .number {
  389. font-size: 24px;
  390. font-weight: bold;
  391. }
  392. .success {
  393. color: green;
  394. }
  395. .danger {
  396. color: red;
  397. }
  398. .warning {
  399. color: orange;
  400. }
  401. .chart-placeholder {
  402. height: 250px;
  403. /*background: #f5f5f5;*/
  404. border-radius: 8px;
  405. }
  406. </style>