index.vue 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. <template>
  2. <div class="alarmHistoryPage">
  3. <div class="searchBar">
  4. <a-form layout="inline" @keydown.enter="searchHandler">
  5. <a-form-item label="创建时间">
  6. <range-picker
  7. v-model:start="searchState.createTimeStart"
  8. v-model:end="searchState.createTimeEnd"
  9. @change="searchHandler"
  10. />
  11. </a-form-item>
  12. <a-form-item label="事件类型">
  13. <a-select
  14. v-model:value="searchState.eventType"
  15. placeholder="请选择"
  16. style="width: 150px"
  17. :options="eventTypeList"
  18. @change="searchHandler"
  19. />
  20. </a-form-item>
  21. <a-form-item>
  22. <a-space>
  23. <a-button type="primary" @click="searchHandler"> 搜索 </a-button>
  24. <a-button @click="resetHandler"> 重置 </a-button>
  25. </a-space>
  26. </a-form-item>
  27. </a-form>
  28. </div>
  29. <div class="tableCard">
  30. <div class="tableCard-header">
  31. <div class="tableCard-header-title">告警历史列表</div>
  32. </div>
  33. <a-table :columns="columns" :data-source="tableList" :loading="loading" :pagination="false">
  34. <template #bodyCell="{ column, record }">
  35. <template v-if="column.key === 'pose'">
  36. {{ record.poseName }}
  37. </template>
  38. <template v-if="column.key === 'eventType'">
  39. {{ record.eventTypeName }}
  40. </template>
  41. <template v-if="column.key === 'info'">
  42. <div class="eventInfo">
  43. <div v-if="record.info?.start_time">开始时间:{{ record.info?.start_time }}</div>
  44. <div v-if="record.info?.end_time">结束时间:{{ record.info?.end_time }}</div>
  45. <div v-if="record.info?.stay_time">停留时长:{{ record.info?.stay_time }}(秒)</div>
  46. <div v-if="record.info?.count">次数:{{ record.info?.count }}</div>
  47. <a-collapse ghost accordion v-if="record.info?.event_list && !initPanel">
  48. <template #expandIcon="{ isActive }">
  49. <caret-right-outlined :rotate="isActive ? 90 : 0" />
  50. </template>
  51. <a-collapse-panel :header="`事件列表 (${record.info?.event_list?.length ?? 0}条)`">
  52. <ScrollContainer style="max-height: 110px">
  53. <div
  54. v-for="(event, index) in record.info?.event_list"
  55. :key="index"
  56. class="eventInfo-list"
  57. >
  58. <div v-if="event.start_time">开始时间:{{ event.start_time }}</div>
  59. <div v-if="event.end_time">结束时间:{{ event.end_time }}</div>
  60. <div v-if="event.stay_time">停留时长:{{ event.stay_time }}(秒)</div>
  61. <div v-if="event.absence_time">消失时长:{{ event.absence_time }}(秒)</div>
  62. <span class="showIndex">{{ index + 1 }}</span>
  63. </div>
  64. </ScrollContainer>
  65. </a-collapse-panel>
  66. </a-collapse>
  67. </div>
  68. </template>
  69. </template>
  70. </a-table>
  71. <base-pagination
  72. v-if="tableTotal > 0"
  73. v-model:current="current"
  74. v-model:pageSize="pageSize"
  75. :total="tableTotal"
  76. @change="paginationChange"
  77. @showSizeChange="paginationSizeChange"
  78. ></base-pagination>
  79. </div>
  80. <alarmPlanModal
  81. v-if="alarmPlanVisible"
  82. v-model:open="alarmPlanVisible"
  83. :title="alarmPlanTitle"
  84. type="template"
  85. :alarm-plan-id="alarmPlanId"
  86. :data="alarmPlanDataWithType"
  87. :area="{
  88. width: 400,
  89. height: 400,
  90. ranges: [-200, 200, -200, 200],
  91. }"
  92. @success="fetchList"
  93. ></alarmPlanModal>
  94. </div>
  95. </template>
  96. <script setup lang="ts">
  97. import { ref, onActivated, computed } from 'vue'
  98. import { columns } from './const'
  99. import { useSearch } from '@/hooks/useSearch'
  100. // import { useRouter } from 'vue-router'
  101. import * as alarmApi from '@/api/alarm'
  102. import alarmPlanModal from '@/views/device/detail/components/alarmPlanModal/index.vue'
  103. // import { message } from 'ant-design-vue'
  104. import { useDictName } from '@/hooks/useDictName'
  105. import * as statsApi from '@/api/stats'
  106. import type { StatsAlarmQueryDataRow } from '@/api/stats/types'
  107. import { CaretRightOutlined } from '@ant-design/icons-vue'
  108. // const router = useRouter()
  109. defineOptions({
  110. name: 'AlarmHistoryIndex',
  111. })
  112. interface SearchData {
  113. createTimeStart: string // 创建时间开始
  114. createTimeEnd: string // 创建时间结束
  115. type?: ID // 事件类型 一般滞留
  116. eventType?: ID // 事件类型 异常滞留
  117. }
  118. const defaultSearch: SearchData = {
  119. createTimeStart: '',
  120. createTimeEnd: '',
  121. type: null,
  122. eventType: null,
  123. }
  124. const [searchState, resetHandler] = useSearch(defaultSearch, { afterReset: () => searchHandler() })
  125. const tableList = ref<StatsAlarmQueryDataRow[]>([])
  126. const tableTotal = ref<number>(0)
  127. const current = ref<number>(1)
  128. const pageSize = ref<number>(10)
  129. const alarmList = ref<StatsAlarmQueryDataRow[]>([])
  130. // 分页变化
  131. const paginationChange = (current: number, pageSize: number) => {
  132. console.log('change', current, pageSize)
  133. fetchList()
  134. }
  135. // 分页大小变化
  136. const paginationSizeChange = (current: number, pageSize: number) => {
  137. console.log('showSizeChange', current, pageSize)
  138. }
  139. const eventTypeList = ref<{ label: string; value: string }[]>([])
  140. // 获取事件类型下拉列表
  141. const fetchEventTypeList = async () => {
  142. try {
  143. const res = await alarmApi.getAlarmEventTypeList()
  144. console.log('获取事件类型下拉列表成功✅', res)
  145. const data = res.data
  146. eventTypeList.value =
  147. (Array.isArray(data) &&
  148. data.map((item) => ({
  149. label: item.eventDesc,
  150. value: item.eventVal,
  151. }))) ||
  152. []
  153. } catch (err) {
  154. console.log('获取事件类型下拉列表失败❌', err)
  155. }
  156. }
  157. const { dictNameMap: alarmEventTypeName } = useDictName(eventTypeList)
  158. const loading = ref(false)
  159. // 获取列表
  160. const fetchList = async () => {
  161. console.log('fetchList')
  162. try {
  163. loading.value = true
  164. initPanel.value = true
  165. const res = await statsApi.statsAlarmQuery({
  166. pageNo: current.value,
  167. pageSize: pageSize.value,
  168. createTimeStart: searchState.createTimeStart,
  169. createTimeEnd: searchState.createTimeEnd,
  170. eventType: searchState.eventType as ID,
  171. })
  172. console.log('✅ 获取告警统计数据成功', res)
  173. const { rows, total } = res.data
  174. alarmList.value = rows as StatsAlarmQueryDataRow[]
  175. alarmList.value.forEach((item) => {
  176. item.eventTypeName = alarmEventTypeName(item.eventType)
  177. try {
  178. item.info = item.info && JSON.parse(item.info)
  179. } catch (error) {
  180. item.info = ''
  181. console.log('❌ 解析info失败', error)
  182. }
  183. })
  184. tableList.value = alarmList.value
  185. tableTotal.value = Number(total)
  186. loading.value = false
  187. initPanel.value = false
  188. } catch (error) {
  189. console.error('❌ 获取告警统计数据失败', error)
  190. loading.value = false
  191. initPanel.value = false
  192. }
  193. }
  194. // 搜索
  195. const searchHandler = async () => {
  196. console.log('searchState', searchState)
  197. current.value = 1
  198. pageSize.value = 10
  199. await fetchList()
  200. }
  201. const alarmPlanVisible = ref(false) // 告警计划弹窗
  202. const alarmPlanId = ref<number | null>(null) // 当前编辑的告警计划id
  203. const alarmPlanTitle = ref<string>('添加告警计划')
  204. type AlarmPlan = {
  205. id: number
  206. uuid: ID
  207. name: string
  208. clientId: string
  209. enable: SwitchType
  210. region: string
  211. eventVal: number
  212. alarmTimePlanId: ID
  213. thresholdTime: ID
  214. mergeTime: ID
  215. param: string
  216. createTime: string
  217. updateTime: string
  218. remark: string | null
  219. alarmTimePlan: {
  220. createId: ID
  221. updateId: ID
  222. createTime: ID
  223. updateTime: ID
  224. isDeleted: SwitchType | null
  225. remark: ID
  226. id: ID
  227. startDate: string
  228. stopDate: string
  229. timeRange: string
  230. monthDays: string
  231. weekdays: string
  232. }
  233. }
  234. interface AlarmPlanItem {
  235. id?: number
  236. loading?: boolean
  237. [key: string]: unknown
  238. }
  239. const alarmPlanData = ref<AlarmPlanItem | undefined>(undefined)
  240. const alarmPlanDataWithType = computed(() => alarmPlanData.value as AlarmPlan)
  241. onActivated(async () => {
  242. await fetchEventTypeList().catch((err) => {
  243. console.error('fetchEventTypeList 失败', err)
  244. })
  245. await fetchList().catch((err) => {
  246. console.error('fetchList 失败', err)
  247. })
  248. })
  249. const initPanel = ref<boolean>(false)
  250. </script>
  251. <style scoped lang="less">
  252. .alarmHistoryPage {
  253. .searchBar {
  254. padding: 20px;
  255. background-color: #fff;
  256. margin-bottom: 20px;
  257. display: flex;
  258. justify-content: space-between;
  259. .ant-form {
  260. flex-grow: 1;
  261. }
  262. :deep(.ant-form-inline .ant-form-item) {
  263. margin-bottom: 16px !important;
  264. }
  265. }
  266. .tableCard {
  267. background-color: #fff;
  268. &-header {
  269. display: flex;
  270. justify-content: space-between;
  271. padding: 20px;
  272. &-title {
  273. font-size: 18px;
  274. font-weight: 600;
  275. }
  276. }
  277. .eventInfo {
  278. display: flex;
  279. flex-direction: column;
  280. align-items: flex-start;
  281. padding: 10px;
  282. border-radius: 4px;
  283. &-list {
  284. display: flex;
  285. flex-direction: column;
  286. align-items: flex-start;
  287. background-color: #eee;
  288. padding: 12px;
  289. border-radius: 5px;
  290. margin-bottom: 8px;
  291. position: relative;
  292. .showIndex {
  293. position: absolute;
  294. right: 0;
  295. bottom: 0;
  296. font-size: 12px;
  297. color: #fff;
  298. padding: 0 5px;
  299. border-radius: 5px 0 5px 0;
  300. background-color: #ccc;
  301. }
  302. }
  303. }
  304. }
  305. }
  306. :deep(.ant-collapse > .ant-collapse-item > .ant-collapse-header) {
  307. padding: 0 !important;
  308. }
  309. :deep(.ant-collapse > .ant-collapse-item > .ant-collapse-header > .ant-collapse-header-text) {
  310. flex: none;
  311. }
  312. :deep(.ant-collapse .ant-collapse-content > .ant-collapse-content-box) {
  313. padding: 0 !important;
  314. }
  315. </style>