zhfxtjfx.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385
  1. <template>
  2. <div class="app-container">
  3. <!-- 搜索区域 -->
  4. <el-form :model="queryParams" ref="queryRef" :inline="true" class="search-form">
  5. <el-form-item label="管网名称">
  6. <el-input v-model="queryParams.name" placeholder="请输入" clearable style="width: 160px" />
  7. </el-form-item>
  8. <el-form-item label="管网类型">
  9. <el-select v-model="queryParams.networkType" placeholder="请选择" clearable style="width: 160px">
  10. <el-option v-for="item in networkTypeOptions" :key="item" :label="item" :value="item" />
  11. </el-select>
  12. </el-form-item>
  13. <el-form-item label="所属区域">
  14. <el-input v-model="queryParams.area" placeholder="请输入" clearable style="width: 160px" />
  15. </el-form-item>
  16. <el-form-item>
  17. <el-button type="primary" @click="handleQuery">搜索</el-button>
  18. <el-button @click="resetQuery">重置</el-button>
  19. </el-form-item>
  20. </el-form>
  21. <!-- 统计概览卡片 -->
  22. <div class="stat-cards">
  23. <el-card class="stat-card stat-blue" shadow="hover">
  24. <div class="stat-card-inner">
  25. <div class="stat-icon"><el-icon :size="32"><DataLine /></el-icon></div>
  26. <div class="stat-info">
  27. <div class="stat-value">{{ stats.total }}</div>
  28. <div class="stat-label">总管网数</div>
  29. </div>
  30. </div>
  31. </el-card>
  32. <el-card class="stat-card stat-red" shadow="hover">
  33. <div class="stat-card-inner">
  34. <div class="stat-icon"><el-icon :size="32"><Warning /></el-icon></div>
  35. <div class="stat-info">
  36. <div class="stat-value">{{ stats.withAlarm }}</div>
  37. <div class="stat-label">有报警管网</div>
  38. </div>
  39. </div>
  40. </el-card>
  41. <el-card class="stat-card stat-orange" shadow="hover">
  42. <div class="stat-card-inner">
  43. <div class="stat-icon"><el-icon :size="32"><WarningFilled /></el-icon></div>
  44. <div class="stat-info">
  45. <div class="stat-value">{{ stats.totalAlarms }}</div>
  46. <div class="stat-label">总报警数</div>
  47. </div>
  48. </div>
  49. </el-card>
  50. <el-card class="stat-card stat-green" shadow="hover">
  51. <div class="stat-card-inner">
  52. <div class="stat-icon"><el-icon :size="32"><CircleCheck /></el-icon></div>
  53. <div class="stat-info">
  54. <div class="stat-value">{{ stats.noAlarm }}</div>
  55. <div class="stat-label">无报警管网</div>
  56. </div>
  57. </div>
  58. </el-card>
  59. </div>
  60. <!-- 数据表格 -->
  61. <el-table :data="tableData" border style="width: 100%" v-loading="loading">
  62. <el-table-column label="序号" type="index" width="60" align="center" :index="(i) => (pageNum - 1) * pageSize + i + 1" />
  63. <el-table-column label="管网名称" prop="name" min-width="180" />
  64. <el-table-column label="所属区域" prop="area" min-width="120" />
  65. <el-table-column label="管网类型" prop="networkType" min-width="100" align="center">
  66. <template #default="{ row }">
  67. <el-tag :type="networkTypeTagType(row.networkType)" effect="light">{{ row.networkType }}</el-tag>
  68. </template>
  69. </el-table-column>
  70. <el-table-column label="关联设备" prop="equipmentId" min-width="100" align="center">
  71. <template #default="{ row }">
  72. <span v-if="row.equipmentId">已关联</span>
  73. <span v-else style="color:#909399">未关联</span>
  74. </template>
  75. </el-table-column>
  76. <el-table-column label="报警次数" min-width="100" align="center">
  77. <template #default="{ row }">
  78. <span :class="row._alarmCount > 0 ? 'alarm-has' : ''">{{ row._alarmCount ?? '-' }}</span>
  79. </template>
  80. </el-table-column>
  81. <el-table-column label="创建时间" prop="createTime" min-width="160" align="center" />
  82. <el-table-column label="操作" width="100" align="center" fixed="right">
  83. <template #default="{ row }">
  84. <el-button link type="primary" @click="handleDetail(row)">查看详情</el-button>
  85. </template>
  86. </el-table-column>
  87. </el-table>
  88. <!-- 分页 -->
  89. <div class="pagination-wrap">
  90. <el-pagination
  91. v-model:current-page="pageNum"
  92. v-model:page-size="pageSize"
  93. :page-sizes="[10, 20, 50]"
  94. :total="total"
  95. layout="total, sizes, prev, pager, next, jumper"
  96. background
  97. @size-change="loadData"
  98. @current-change="loadData"
  99. />
  100. </div>
  101. <!-- 详情弹窗 -->
  102. <el-drawer v-model="detailVisible" title="管网综合风险详情" size="800px" destroy-on-close class="detail-drawer" direction="rtl">
  103. <template v-if="currentRow">
  104. <el-tabs v-model="detailTab">
  105. <el-tab-pane label="基本信息" name="basic">
  106. <el-descriptions :column="2" border>
  107. <el-descriptions-item label="管网名称">{{ currentRow.name }}</el-descriptions-item>
  108. <el-descriptions-item label="所属区域">{{ currentRow.area }}</el-descriptions-item>
  109. <el-descriptions-item label="管网类型">
  110. <el-tag :type="networkTypeTagType(currentRow.networkType)" effect="light">{{ currentRow.networkType }}</el-tag>
  111. </el-descriptions-item>
  112. <el-descriptions-item label="关联设备">{{ currentRow.equipmentId ? '已关联 (ID: ' + currentRow.equipmentId + ')' : '未关联' }}</el-descriptions-item>
  113. <el-descriptions-item label="坐标信息" :span="2">{{ formatPoints(currentRow.points) }}</el-descriptions-item>
  114. <el-descriptions-item label="创建时间">{{ currentRow.createTime }}</el-descriptions-item>
  115. <el-descriptions-item label="更新时间">{{ currentRow.updateTime }}</el-descriptions-item>
  116. </el-descriptions>
  117. </el-tab-pane>
  118. <el-tab-pane label="报警统计" name="alarm">
  119. <div v-if="currentRow.equipmentId" v-loading="alarmLoading">
  120. <div style="margin-bottom:16px">
  121. <el-tag :type="currentRow._alarmCount > 0 ? 'danger' : 'success'" size="large">
  122. 报警总数:{{ currentRow._alarmCount ?? 0 }}
  123. </el-tag>
  124. </div>
  125. <el-table :data="alarmList" border size="small" v-if="alarmList.length > 0">
  126. <el-table-column prop="alarmTime" label="报警时间" min-width="160" />
  127. <el-table-column prop="alarmType" label="报警类型" min-width="100" />
  128. <el-table-column prop="alarmContent" label="报警内容" min-width="200" />
  129. <el-table-column prop="status" label="状态" min-width="80" align="center">
  130. <template #default="{ row }">
  131. <el-tag :type="row.status === 0 ? 'warning' : row.status === 1 ? 'primary' : 'success'" size="small">
  132. {{ row.status === 0 ? '未处理' : row.status === 1 ? '处理中' : '已处理' }}
  133. </el-tag>
  134. </template>
  135. </el-table-column>
  136. </el-table>
  137. <el-empty v-else description="暂无报警数据" :image-size="60" />
  138. </div>
  139. <el-empty v-else description="该管网未关联设备,无报警数据" :image-size="60" />
  140. </el-tab-pane>
  141. </el-tabs>
  142. </template>
  143. <template #footer>
  144. <el-button @click="detailVisible = false">关 闭</el-button>
  145. </template>
  146. </el-drawer>
  147. </div>
  148. </template>
  149. <script setup name="Zhfxtjfx">
  150. import { ref, computed, onMounted } from 'vue'
  151. import { useRouter } from 'vue-router'
  152. import { DataLine, Warning, WarningFilled, CircleCheck } from '@element-plus/icons-vue'
  153. import { getPipeNetworkPage, getAlarmDataPage } from '@/api/drainage'
  154. const router = useRouter()
  155. // ==================== 下拉选项 ====================
  156. const networkTypeOptions = ['管线', '管点', '排口']
  157. // ==================== 搜索参数 ====================
  158. const queryParams = ref({ name: '', networkType: '管线', area: '' })
  159. const queryRef = ref(null)
  160. // ==================== 分页与数据 ====================
  161. const pageNum = ref(1)
  162. const pageSize = ref(10)
  163. const total = ref(0)
  164. const tableData = ref([])
  165. const loading = ref(false)
  166. const handleQuery = () => {
  167. pageNum.value = 1
  168. loadData()
  169. }
  170. const resetQuery = () => {
  171. queryParams.value = { name: '', networkType: '管线', area: '' }
  172. pageNum.value = 1
  173. loadData()
  174. }
  175. // ==================== 加载数据 ====================
  176. const loadData = async () => {
  177. loading.value = true
  178. try {
  179. const params = {}
  180. if (queryParams.value.name) params.name = queryParams.value.name
  181. if (queryParams.value.networkType) params.networkType = queryParams.value.networkType
  182. if (queryParams.value.area) params.area = queryParams.value.area
  183. const res = await getPipeNetworkPage(pageNum.value, pageSize.value, params)
  184. const pageData = res.code !== undefined ? res.data : res
  185. const rows = pageData.records || pageData.rows || []
  186. const totalCount = pageData.total ?? 0
  187. total.value = totalCount
  188. // 为每条管网查询报警数量
  189. const withAlarms = await Promise.all(
  190. rows.map(async (item) => {
  191. let alarmCount = 0
  192. if (item.equipmentId) {
  193. try {
  194. const alarmRes = await getAlarmDataPage(1, 1, { equipmentId: item.equipmentId, equipmentType: 'drainage' })
  195. const alarmPageData = alarmRes.code !== undefined ? alarmRes.data : alarmRes
  196. alarmCount = alarmPageData.total ?? 0
  197. } catch (e) { /* ignore */ }
  198. }
  199. return { ...item, _alarmCount: alarmCount }
  200. })
  201. )
  202. tableData.value = withAlarms
  203. } catch (e) {
  204. console.error('加载管网数据失败', e)
  205. tableData.value = []
  206. } finally {
  207. loading.value = false
  208. }
  209. }
  210. // ==================== 统计 ====================
  211. const stats = computed(() => {
  212. const list = tableData.value
  213. const withAlarm = list.filter(i => i._alarmCount > 0).length
  214. const totalAlarms = list.reduce((sum, i) => sum + (i._alarmCount || 0), 0)
  215. return {
  216. total: total.value,
  217. withAlarm,
  218. totalAlarms,
  219. noAlarm: list.length - withAlarm
  220. }
  221. })
  222. // ==================== 工具函数 ====================
  223. const networkTypeTagType = (type) => {
  224. const map = { '管线': '', '管点': 'warning', '排口': 'success' }
  225. return map[type] || ''
  226. }
  227. const formatPoints = (points) => {
  228. if (!points) return '暂无坐标'
  229. try {
  230. const parsed = typeof points === 'string' ? JSON.parse(points) : points
  231. if (Array.isArray(parsed)) {
  232. return parsed.map(p => `${p.lng || p[0]}, ${p.lat || p[1]}`).join(' → ')
  233. }
  234. return String(points)
  235. } catch {
  236. return String(points)
  237. }
  238. }
  239. // ==================== 详情弹窗 ====================
  240. const detailVisible = ref(false)
  241. const currentRow = ref(null)
  242. const detailTab = ref('basic')
  243. const alarmLoading = ref(false)
  244. const alarmList = ref([])
  245. const handleDetail = async (row) => {
  246. currentRow.value = row
  247. detailTab.value = 'basic'
  248. detailVisible.value = true
  249. alarmList.value = []
  250. if (row.equipmentId) {
  251. alarmLoading.value = true
  252. try {
  253. const res = await getAlarmDataPage(1, 20, { equipmentId: row.equipmentId, equipmentType: 'drainage' })
  254. alarmList.value = res.rows || res.data?.rows || res.data || []
  255. } catch (e) {
  256. alarmList.value = []
  257. } finally {
  258. alarmLoading.value = false
  259. }
  260. }
  261. }
  262. // ==================== 初始化 ====================
  263. onMounted(() => {
  264. loadData()
  265. })
  266. </script>
  267. <style scoped>
  268. .app-container {
  269. padding: 20px;
  270. }
  271. /* 搜索表单 */
  272. .search-form {
  273. margin-bottom: 16px;
  274. }
  275. /* 统计卡片 */
  276. .stat-cards {
  277. display: flex;
  278. gap: 16px;
  279. margin-bottom: 20px;
  280. }
  281. .stat-card {
  282. flex: 1;
  283. min-width: 0;
  284. }
  285. .stat-card :deep(.el-card__body) {
  286. padding: 16px 20px;
  287. }
  288. .stat-card-inner {
  289. display: flex;
  290. align-items: center;
  291. gap: 16px;
  292. }
  293. .stat-icon {
  294. width: 56px;
  295. height: 56px;
  296. border-radius: 12px;
  297. display: flex;
  298. align-items: center;
  299. justify-content: center;
  300. flex-shrink: 0;
  301. }
  302. .stat-blue .stat-icon {
  303. background: rgba(64, 158, 255, 0.1);
  304. color: #409EFF;
  305. }
  306. .stat-red .stat-icon {
  307. background: rgba(245, 108, 108, 0.1);
  308. color: #F56C6C;
  309. }
  310. .stat-orange .stat-icon {
  311. background: rgba(230, 162, 60, 0.1);
  312. color: #E6A23C;
  313. }
  314. .stat-green .stat-icon {
  315. background: rgba(103, 194, 58, 0.1);
  316. color: #67C23A;
  317. }
  318. .stat-info {
  319. flex: 1;
  320. min-width: 0;
  321. }
  322. .stat-value {
  323. font-size: 28px;
  324. font-weight: 700;
  325. line-height: 1.2;
  326. }
  327. .stat-blue .stat-value { color: #409EFF; }
  328. .stat-red .stat-value { color: #F56C6C; }
  329. .stat-orange .stat-value { color: #E6A23C; }
  330. .stat-green .stat-value { color: #67C23A; }
  331. .stat-label {
  332. font-size: 14px;
  333. color: #909399;
  334. margin-top: 4px;
  335. }
  336. /* 报警数高亮 */
  337. .alarm-has {
  338. color: #F56C6C;
  339. font-weight: 700;
  340. }
  341. /* 分页 */
  342. .pagination-wrap {
  343. display: flex;
  344. justify-content: flex-end;
  345. margin-top: 16px;
  346. }
  347. </style>