瀏覽代碼

设备管理统一管理

zengy 4 周之前
父節點
當前提交
79bb7e520c

+ 0 - 20
src/api/drainage.js

@@ -101,32 +101,12 @@ export function getEquipmentById(id) {
 export function getEquipmentList() {
   return request({ url: '/api/drainage/equipment/base/list', method: 'get' })
 }
-export function addEquipment(data) {
-  return request({ url: '/api/drainage/equipment/base', method: 'post', data })
-}
-export function updateEquipment(data) {
-  return request({ url: '/api/drainage/equipment/base', method: 'put', data })
-}
-export function deleteEquipment(id) {
-  return request({ url: '/api/drainage/equipment/base/' + id, method: 'delete' })
-}
-export function batchDeleteEquipment(ids) {
-  return request({ url: '/api/drainage/equipment/base/batch', method: 'delete', data: ids })
-}
 export function getEquipmentTypeList(parentTypeId) {
   const params = {}
   if (parentTypeId) params.parentTypeId = parentTypeId
   return request({ url: '/api/drainage/equipmentType/list', method: 'get', params })
 }
 
-// ============ 设备状态 ============
-export function addEquipmentStatus(data) {
-  return request({ url: '/api/drainage/equipment/status', method: 'post', data })
-}
-export function updateEquipmentStatus(data) {
-  return request({ url: '/api/drainage/equipment/status', method: 'put', data })
-}
-
 // ============ 预警管理 ============
 
 // 分页查询当前预警(今天的预警)

+ 4 - 46
src/api/pipeNetwork/basic.js

@@ -47,14 +47,6 @@ export function deleteManhole(id) {
 }
 
 // ============ 窨井井下设备关联 ============
-// 查询设备已关联的井下设备列表
-export function getManholeDeviceRels(equipmentId) {
-  return request({ url: '/manhole/device/rel/list/' + equipmentId, method: 'get' })
-}
-// 搜索可关联设备(排除窨井类型5/9/10,排除已关联设备ID)
-export function searchManholeRelDevices(params) {
-  return request({ url: '/manhole/device/rel/search', method: 'get', params })
-}
 // 查询窨井关联的设备列表(兼容 Manhole.vue,manholeId 为 ManholeBase 的 ID)
 export function getManholeDevices(manholeId) {
   return request({ url: '/manhole/device/rel/list/' + manholeId, method: 'get' })
@@ -73,10 +65,6 @@ export function deleteManholeDeviceRel(relId) {
 export function getManholeDevicePage(data) {
   return request({ url: '/manhole/device/findByPage', method: 'post', data })
 }
-// 监测设备台账详情
-export function getManholeDeviceById(id) {
-  return request({ url: '/manhole/device/getById/' + id, method: 'get' })
-}
 // 窨井设备统计:区域分布(数量/占比)+ 类型分布(数量/占比)
 export function getManholeDeviceStatistics(data) {
   return request({ url: '/manhole/device/statistics', method: 'post', data })
@@ -313,50 +301,23 @@ export function deleteRepairRecord(id) {
 
 // ============ 监测设备管理 ============
 export function getEquipmentList() {
-  return request({ url: '/EquipmentBase/getList', method: 'get' })
+  return request({ url: '/equipment-base/all', method: 'get' })
 }
 export function getEquipmentPage(pageNum, pageSize, params) {
-  return request({ url: '/EquipmentBase/findByPage', method: 'get', params: { pageNum, pageSize, ...params } })
-}
-export function getEquipmentFullPage(pageNum, pageSize, params) {
-  return request({ url: '/api/equipment/findFullByPage', method: 'get', params: { pageNum, pageSize, ...params } })
+  return request({ url: '/equipment-base', method: 'get', params: { pageNum, pageSize, ...params } })
 }
 export function getEquipmentById(id) {
-  return request({ url: '/EquipmentBase/getById/' + id, method: 'get' })
-}
-export function addEquipment(data) {
-  const params = {}
-  if (data.networkId) params.networkId = data.networkId
-  if (data.pointId) params.pointId = data.pointId
-  return request({ url: '/api/equipment/save', method: 'post', data, params })
-}
-export function updateEquipment(data) {
-  const params = {}
-  if (data.networkId) params.networkId = data.networkId
-  if (data.pointId) params.pointId = data.pointId
-  return request({ url: '/api/equipment/updateById', method: 'put', data, params })
-}
-export function deleteEquipment(id) {
-  return request({ url: '/api/equipment/deleteById', method: 'delete', params: { id } })
+  return request({ url: '/equipment-base/' + id, method: 'get' })
 }
 export function getEquipmentTypeList() {
   return request({ url: '/EquipmentType/getList', method: 'get' })
 }
 export function getEquipmentByTopLevelType(typeName) {
-  return request({ url: '/EquipmentBase/findByTopLevelType', method: 'get', params: { typeName } })
+  return request({ url: '/equipment-base/top-level-type', method: 'get', params: { typeName } })
 }
 export function getGasDashboardSummary() {
   return request({ url: '/api/gas-dashboard/summary', method: 'get' })
 }
-export function getEquipmentStatus(equipmentId) {
-  return request({ url: '/api/equipment/status/' + equipmentId, method: 'get' })
-}
-export function getEquipmentAbnormalList() {
-  return request({ url: '/api/equipment/abnormal', method: 'get' })
-}
-export function getPointRelByEquipment(equipmentId) {
-  return request({ url: '/api/equipment/point-rel/' + equipmentId, method: 'get' })
-}
 export function getEquipmentByPoint(pointId) {
   return request({ url: '/api/equipment/point-rel/by-point/' + pointId, method: 'get' })
 }
@@ -367,9 +328,6 @@ export function getPipePointDevices(pointId) {
 export function addPipePointDeviceRel(data) {
   return request({ url: '/api/gas-pipe-point/device/save', method: 'post', data })
 }
-export function saveEquipmentDeviceRel(data) {
-  return request({ url: '/api/equipment/device-rel/save', method: 'post', data })
-}
 export function addPipePointDeviceRelBatch(dataList) {
   return request({ url: '/api/gas-pipe-point/device/save-batch', method: 'post', dataList })
 }

+ 24 - 28
src/api/pipeNetwork/pdeviceInform.js

@@ -130,10 +130,10 @@ export function unbindPointEquipment(pointId, equipmentIds) {
   });
 }
 
-// 物联感知设备
+// 物联感知设备(RESTful)
 export function listEquipment(query) {
   return request({
-    url: "/EquipmentBase/findByPage",
+    url: "/equipment-base",
     method: "get",
     params: query
   });
@@ -141,14 +141,14 @@ export function listEquipment(query) {
 
 export function getEquipment(id) {
   return request({
-    url: `/EquipmentBase/getById/${id}`,
+    url: `/equipment-base/${id}`,
     method: "get"
   });
 }
 
 export function addEquipment(data) {
   return request({
-    url: "/EquipmentBase/save",
+    url: "/equipment-base",
     method: "post",
     data
   });
@@ -156,7 +156,7 @@ export function addEquipment(data) {
 
 export function updateEquipment(data) {
   return request({
-    url: "/EquipmentBase/updateById",
+    url: `/equipment-base/${data.equipmentId}`,
     method: "put",
     data
   });
@@ -164,58 +164,54 @@ export function updateEquipment(data) {
 
 export function delEquipment(id) {
   return request({
-    url: "/EquipmentBase/deleteById",
-    method: "delete",
-    params: { id }
+    url: `/equipment-base/${id}`,
+    method: "delete"
   });
 }
 
 export function getEquipmentDetail(equipmentId) {
   return request({
-    url: `/EquipmentBase/detail/${equipmentId}`,
+    url: `/equipment-base/${equipmentId}/detail`,
     method: "get"
   });
 }
 
 export function listEquipmentByPoint(pointId) {
   return request({
-    url: `/EquipmentBase/byPoint/${pointId}`,
+    url: `/equipment-base/point/${pointId}`,
     method: "get"
   });
 }
 
 export function updateEquipmentStatus(equipmentId, currentStatus) {
   return request({
-    url: "/EquipmentBase/updateStatus",
+    url: `/equipment-base/${equipmentId}/status`,
     method: "put",
-    params: { equipmentId, currentStatus }
+    params: { currentStatus }
   });
 }
 
-export function testEquipment(equipmentId, payload = {}) {
+export function listEquipmentStatusAll(rootTypeId) {
   return request({
-    url: "/EquipmentBase/test",
-    method: "post",
-    params: {
-      equipmentId,
-      testType: payload.testType,
-      testDetail: payload.testDetail,
-      testUser: payload.testUser
-    }
+    url: "/equipment-status/all",
+    method: "get",
+    params: { rootTypeId }
   });
 }
 
-// 基础辅助数据
-export function listEquipmentTypeAll() {
+export function getEquipmentStatusStatistics(rootTypeId) {
   return request({
-    url: "/EquipmentType/getList",
-    method: "get"
+    url: "/equipment-status/statistics",
+    method: "get",
+    params: { rootTypeId }
   });
 }
 
-export function listEquipmentStatusAll() {
+// 基础辅助数据
+export function listEquipmentTypeAll(rootTypeId) {
   return request({
-    url: "/EquipmentStatus/getList",
-    method: "get"
+    url: "/EquipmentType/getList",
+    method: "get",
+    params: { rootTypeId }
   });
 }

+ 1 - 26
src/api/pipeNetwork/waterSupply.js

@@ -32,32 +32,7 @@ export const listAlarmLedger = (params) => get('/waterSupply/alarm/ledger/page',
 export const getAlarmLedgerDashboard = (days = 7) => get('/waterSupply/alarm/ledger/dashboard', { days })
 export const exportAlarmLedger = (params) => request({ url: '/waterSupply/alarm/ledger/export', method: 'post', params, responseType: 'blob' })
 
-export const createWaterDevice = (data) => request({ url: '/waterSupply/device/ledger/create', method: 'post', data })
-export const getWaterDeviceById = (id) => get(`/EquipmentBase/getById/${id}`)
-export const updateWaterDevice = (data) => request({ url: '/EquipmentBase/updateById', method: 'put', data })
-export const deleteWaterDevices = (ids) => request({ url: '/EquipmentBase/deleteBatchById', method: 'delete', params: { ids: Array.isArray(ids) ? ids.join(',') : ids } })
-export const getDeviceLedgerStatistics = () => get('/waterSupply/device/ledger/statistics')
-export const listDeviceLedger = (params) => get('/waterSupply/device/ledger/page', params)
-export const getDeviceTypeStatistics = () => get('/waterSupply/device/ledger/typeStatistics')
-export const listDeviceGisPoints = (params) => get('/waterSupply/device/ledger/gisPoints', params)
-export const exportDeviceLedger = (params) => request({ url: '/waterSupply/device/ledger/export', method: 'post', params, responseType: 'blob' })
-export const importDeviceLedger = (file, updateSupport = false) => {
-  const formData = new FormData()
-  formData.append('file', file)
-  formData.append('updateSupport', String(updateSupport))
-  return request({ url: '/waterSupply/device/ledger/import', method: 'post', data: formData })
-}
-export const listAbnormalDevices = (params) => get('/waterSupply/device/abnormal/page', params)
-export const getAbnormalDeviceStats = () => get('/waterSupply/device/abnormal/stats')
-export const listAbnormalGisPoints = (params) => get('/waterSupply/device/abnormal/gisPoints', params)
-export const exportAbnormalDevices = (params) => request({ url: '/waterSupply/device/abnormal/export', method: 'post', params, responseType: 'blob' })
-export const createMaintainOrder = (data) => request({ url: '/waterSupply/device/maintain/order', method: 'post', data })
-export const createAbnormalOrder = (data) => request({ url: '/waterSupply/device/abnormal/order', method: 'post', data })
-export const listMaintainRecords = (params) => get('/waterSupply/device/maintain/record/page', params)
-export const saveMaintainRecord = (data) => request({ url: '/waterSupply/device/maintain/record', method: 'post', data })
-export const updateMaintainRecord = (data) => request({ url: '/waterSupply/device/maintain/record', method: 'put', data })
-export const deleteMaintainRecord = (id) => request({ url: `/waterSupply/device/maintain/record/${id}`, method: 'delete' })
-
+// 供水设备 GIS 一张图(WaterDeviceGisMap / WaterSupplyHome 使用)
 export const getPipeNetworkDashboard = (params) => get('/waterSupply/pipeNetwork/statistics/dashboard', params)
 export const exportPipeNetworkStatistics = (params) => request({ url: '/waterSupply/pipeNetwork/statistics/export', method: 'get', params, responseType: 'blob' })
 export const getWaterFacilityFeatures = (params) => get('/waterSupply/gis/map/features', params)

+ 0 - 34
src/router/modules/waterSupplyRouter.js

@@ -1,34 +0,0 @@
-import Layout from '@/layout'
-
-// 供水管网安全运行监测系统 - 前端静态菜单
-// 说明:如果后端动态菜单已配置,可移除本文件或保留为兜底入口。
-export default [
-  {
-    path: '/waterSupply',
-    component: Layout,
-    redirect: '/waterSupply/facility/gis',
-    name: 'WaterSupply',
-    alwaysShow: true,
-    meta: { title: '供水管网安全运行监测系统', icon: 'example' },
-    children: [
-      {
-        path: 'facility/gis',
-        component: () => import('@/views/subSystem/waterSupply/facility/gis.vue'),
-        name: 'WaterSupplyFacilityGis',
-        meta: { title: '供水设施GIS一张图', icon: 'map' }
-      },
-      {
-        path: 'facility/statistics',
-        component: () => import('@/views/subSystem/waterSupply/facility/statistics.vue'),
-        name: 'WaterSupplyFacilityStatistics',
-        meta: { title: '管网数据统计分析', icon: 'chart' }
-      },
-      {
-        path: 'monitorAlarm/workOrder',
-        component: () => import('@/views/subSystem/waterSupply/monitorAlarm/workOrder.vue'),
-        name: 'WaterSupplyMonitorAlarmWorkOrder',
-        meta: { title: '报警工单处置', icon: 'work-order' }
-      }
-    ]
-  }
-]

+ 0 - 605
src/views/subSystem/basic/Equipment.vue

@@ -1,605 +0,0 @@
-<template>
-  <div class="equipment-container">
-    <el-card class="search-card">
-      <el-form :inline="true" :model="searchForm" class="search-form">
-        <el-form-item label="设备编码">
-          <el-input v-model="searchForm.equipmentCode" placeholder="请输入设备编码" clearable style="width:180px" />
-        </el-form-item>
-        <el-form-item label="设备名称">
-          <el-input v-model="searchForm.equipmentName" placeholder="请输入设备名称" clearable style="width:180px" />
-        </el-form-item>
-        <el-form-item label="设备型号">
-          <el-input v-model="searchForm.equipmentModel" placeholder="请输入设备型号" clearable style="width:160px" />
-        </el-form-item>
-        <el-form-item label="制造商">
-          <el-input v-model="searchForm.manufacturer" placeholder="请输入制造商" clearable style="width:160px" />
-        </el-form-item>
-        <el-form-item label="所属片区">
-          <el-select v-model="searchForm.district" placeholder="请选择所属片区" clearable style="width:120px">
-            <el-option v-for="opt in districtOptions" :key="opt.value" :label="opt.label" :value="opt.value" />
-          </el-select>
-        </el-form-item>
-        <el-form-item>
-          <el-button type="primary" @click="handleSearch">查询</el-button>
-          <el-button @click="handleReset">重置</el-button>
-          <el-button type="success" @click="handleAdd">新增</el-button>
-        </el-form-item>
-      </el-form>
-    </el-card>
-
-    <el-card class="table-card">
-      <el-table :data="tableData" border stripe v-loading="loading" style="width: 100%">
-        <el-table-column prop="equipmentCode" label="设备编码" min-width="130" />
-        <el-table-column prop="district" label="所属片区" min-width="100">
-          <template #default="{ row }">
-            <span>{{ row.district || '-' }}</span>
-          </template>
-        </el-table-column>
-        <el-table-column label="设备图片" width="100">
-          <template #default="{ row }">
-            <div v-if="row.equipmentImage" class="table-image-cell">
-              <el-image
-                :src="row.equipmentImage"
-                fit="cover"
-                :preview-src-list="[row.equipmentImage]"
-                preview-teleported
-                hide-on-click-pdf
-                class="equipment-table-image"
-              />
-            </div>
-            <el-tag v-else type="info" size="small">无图</el-tag>
-          </template>
-        </el-table-column>
-        <el-table-column prop="equipmentName" label="设备名称" min-width="160" />
-        <el-table-column prop="equipmentModel" label="设备型号" width="110" />
-        <el-table-column prop="manufacturer" label="制造商" width="110" />
-        <el-table-column prop="networkName" label="所属管线" min-width="150">
-          <template #default="{ row }">
-            <span v-if="row.networkName">{{ row.networkName }}</span>
-            <el-tag v-else type="info" size="small">未关联</el-tag>
-          </template>
-        </el-table-column>
-        <el-table-column prop="pointName" label="所属管点" min-width="150">
-          <template #default="{ row }">
-            <span v-if="row.pointName">{{ row.pointName }}</span>
-            <el-tag v-else type="info" size="small">未关联</el-tag>
-          </template>
-        </el-table-column>
-        <el-table-column label="运行状态" width="90">
-          <template #default="{ row }">
-            <el-tag v-if="row.currentStatus === 1" type="success" size="small">在用</el-tag>
-            <el-tag v-else-if="row.currentStatus === 2" type="warning" size="small">闲置</el-tag>
-            <el-tag v-else-if="row.currentStatus === 3" type="danger" size="small">维修</el-tag>
-            <el-tag v-else-if="row.currentStatus === 4" type="info" size="small">报废</el-tag>
-            <el-tag v-else-if="row.currentStatus === 5" type="warning" size="small">待入库</el-tag>
-            <el-tag v-else type="info" size="small">未知</el-tag>
-          </template>
-        </el-table-column>
-        <el-table-column label="报警" width="70">
-          <template #default="{ row }">
-            <el-tag :type="row.alarmStatus === 1 ? 'danger' : 'success'" size="small">
-              {{ row.alarmStatus === 1 ? '报警' : '正常' }}
-            </el-tag>
-          </template>
-        </el-table-column>
-        <el-table-column label="在线" width="70">
-          <template #default="{ row }">
-            <el-tag :type="row.onlineStatus === 1 ? 'success' : 'info'" size="small">
-              {{ row.onlineStatus === 1 ? '在线' : '离线' }}
-            </el-tag>
-          </template>
-        </el-table-column>
-        <el-table-column label="操作" width="220" fixed="right">
-          <template #default="{ row }">
-            <el-button type="primary" size="small" @click="handleEdit(row)">编辑</el-button>
-            <el-button type="info" size="small" @click="handleDetail(row)">详情</el-button>
-            <el-button type="danger" size="small" @click="handleDelete(row)">删除</el-button>
-          </template>
-        </el-table-column>
-      </el-table>
-      <el-pagination
-        v-model:current-page="pageNum" v-model:page-size="pageSize" :total="total"
-        layout="total, prev, pager, next, sizes" :page-sizes="[10, 20, 50]"
-        @change="loadData" style="margin-top: 16px; justify-content: flex-end"
-      />
-    </el-card>
-
-    <el-drawer title="设备详情" v-model="detailVisible" size="1100px" class="detail-drawer" direction="rtl">
-      <el-descriptions v-if="detailData" :column="2" border>
-        <el-descriptions-item label="设备编码" :span="1">{{ detailData.equipmentCode }}</el-descriptions-item>
-        <el-descriptions-item label="设备名称" :span="1">{{ detailData.equipmentName }}</el-descriptions-item>
-        <el-descriptions-item label="设备型号">{{ detailData.equipmentModel }}</el-descriptions-item>
-        <el-descriptions-item label="设备类别">{{ detailData.typeName || '-' }}</el-descriptions-item>
-        <el-descriptions-item label="规格参数">{{ detailData.equipmentSpec }}</el-descriptions-item>
-        <el-descriptions-item label="制造商">{{ detailData.manufacturer }}</el-descriptions-item>
-        <el-descriptions-item label="生产日期">{{ detailData.productionDate }}</el-descriptions-item>
-        <el-descriptions-item label="所属管线">{{ detailData.networkName || '未关联' }}</el-descriptions-item>
-        <el-descriptions-item label="管线等级">{{ detailData.networkLevel || '-' }}</el-descriptions-item>
-        <el-descriptions-item label="所属管点">{{ detailData.pointName || '未关联' }}</el-descriptions-item>
-        <el-descriptions-item label="管点类型">{{ detailData.pointType || '-' }}</el-descriptions-item>
-        <el-descriptions-item label="安装位置">{{ detailData.equipmentLocation }}</el-descriptions-item>
-        <el-descriptions-item label="安装时间">{{ detailData.installTime }}</el-descriptions-item>
-        <el-descriptions-item label="运行状态">
-          <el-tag v-if="detailData.currentStatus === 1" type="success" size="small">在用</el-tag>
-          <el-tag v-else-if="detailData.currentStatus === 2" type="warning" size="small">闲置</el-tag>
-          <el-tag v-else-if="detailData.currentStatus === 3" type="danger" size="small">维修</el-tag>
-          <el-tag v-else-if="detailData.currentStatus === 4" type="info" size="small">报废</el-tag>
-          <el-tag v-else-if="detailData.currentStatus === 5" type="warning" size="small">待入库</el-tag>
-          <span v-else>-</span>
-        </el-descriptions-item>
-        <el-descriptions-item label="报警状态">
-          <el-tag :type="detailData.alarmStatus === 1 ? 'danger' : 'success'" size="small">
-            {{ detailData.alarmStatus === 1 ? '报警' : '正常' }}
-          </el-tag>
-        </el-descriptions-item>
-        <el-descriptions-item label="在线状态">
-          <el-tag :type="detailData.onlineStatus === 1 ? 'success' : 'info'" size="small">
-            {{ detailData.onlineStatus === 1 ? '在线' : '离线' }}
-          </el-tag>
-        </el-descriptions-item>
-        <el-descriptions-item label="维护人员">{{ detailData.maintainer || '-' }}</el-descriptions-item>
-        <el-descriptions-item label="联系方式">{{ detailData.maintainerPhone || '-' }}</el-descriptions-item>
-        <el-descriptions-item label="所属片区">{{ detailData.district || '-' }}</el-descriptions-item>
-        <el-descriptions-item label="经度">{{ detailData.longitude || '-' }}</el-descriptions-item>
-        <el-descriptions-item label="纬度">{{ detailData.latitude || '-' }}</el-descriptions-item>
-        <el-descriptions-item label="设备图片" :span="2">
-          <el-image
-            v-if="detailData.equipmentImage"
-            :src="detailData.equipmentImage"
-            fit="cover"
-            :preview-src-list="[detailData.equipmentImage]"
-            preview-teleported
-            class="detail-equipment-image"
-          />
-          <span v-else>-</span>
-        </el-descriptions-item>
-        <el-descriptions-item label="备注" :span="2">{{ detailData.remark || '-' }}</el-descriptions-item>
-      </el-descriptions>
-    </el-drawer>
-
-    <el-dialog :title="dialogTitle" v-model="dialogVisible" width="1100px" destroy-on-close>
-      <el-form :model="formData" :rules="rules" ref="formRef" label-width="110px" size="default">
-        <el-row :gutter="28">
-          <el-col :span="12">
-            <el-form-item label="所属片区" prop="district">
-              <el-select v-model="formData.district" placeholder="请选择所属片区" style="width:100%" @change="onDistrictChange">
-                <el-option v-for="opt in districtOptions" :key="opt.value" :label="opt.label" :value="opt.value" />
-              </el-select>
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="设备编码" prop="equipmentCode">
-              <el-input v-model="formData.equipmentCode" placeholder="保存时自动生成" readonly />
-            </el-form-item>
-          </el-col>
-        </el-row>
-        <el-row :gutter="28">
-          <el-col :span="12">
-            <el-form-item label="设备名称" prop="equipmentName">
-              <el-input v-model="formData.equipmentName" placeholder="请输入设备名称" />
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="设备型号">
-              <el-input v-model="formData.equipmentModel" />
-            </el-form-item>
-          </el-col>
-        </el-row>
-        <el-row :gutter="28">
-          <el-col :span="12">
-            <el-form-item label="规格参数">
-              <el-input v-model="formData.equipmentSpec" />
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="制造商">
-              <el-input v-model="formData.manufacturer" />
-            </el-form-item>
-          </el-col>
-        </el-row>
-        <el-row :gutter="28">
-          <el-col :span="12">
-            <el-form-item label="设备类别" prop="equipmentTypeId">
-              <el-select v-model="formData.equipmentTypeId" placeholder="请选择设备类别" style="width:100%">
-                <el-option v-for="t in equipmentTypeOptions" :key="t.id" :label="t.typeName" :value="t.id" />
-              </el-select>
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="安装位置">
-              <el-input v-model="formData.equipmentLocation" />
-            </el-form-item>
-          </el-col>
-        </el-row>
-        <el-row :gutter="28">
-          <el-col :span="12">
-            <el-form-item label="维护人员">
-              <el-input v-model="formData.maintainer" />
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="所属管线">
-              <el-select v-model="formData.networkId" placeholder="请选择管线" clearable style="width:100%" @change="onNetworkChange">
-                <el-option v-for="n in networkOptions" :key="n.networkId" :label="`${n.networkName}(${n.networkCode})`" :value="n.networkId" />
-              </el-select>
-            </el-form-item>
-          </el-col>
-        </el-row>
-        <el-row :gutter="28">
-          <el-col :span="12">
-            <el-form-item label="关联管点">
-              <el-select v-model="formData.pointId" placeholder="请先选择管线" clearable style="width:100%">
-                <el-option v-for="p in pointOptions" :key="p.pointId" :label="`${p.pointName}(${p.pointCode})`" :value="p.pointId" />
-              </el-select>
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="经度">
-              <el-input-number v-model="formData.longitude" :precision="10" style="width:100%" />
-            </el-form-item>
-          </el-col>
-        </el-row>
-        <el-row :gutter="28">
-          <el-col :span="12">
-            <el-form-item label="纬度">
-              <el-input-number v-model="formData.latitude" :precision="10" style="width:100%" />
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="联系方式">
-              <el-input v-model="formData.maintainerPhone" />
-            </el-form-item>
-          </el-col>
-        </el-row>
-        <el-form-item label="备注">
-          <el-input v-model="formData.remark" type="textarea" :rows="3" />
-        </el-form-item>
-        <el-form-item label="设备图片">
-          <el-upload
-            class="image-uploader"
-            action="#"
-            :auto-upload="false"
-            :show-file-list="false"
-            :http-request="handleUploadRequest"
-            :on-change="handleImageChange"
-            :on-remove="handleImageRemove"
-            accept="image/jpeg,image/png,image/jpg"
-          >
-            <div v-if="formData.equipmentImage || equipmentImagePreview">
-              <img :src="equipmentImagePreview || formData.equipmentImage" alt="设备图片" class="preview-image" />
-              <div class="image-uploader-cover">
-                <el-icon><Plus /></el-icon>
-              </div>
-            </div>
-            <div v-else class="image-uploader-placeholder">
-              <el-icon class="el-icon--upload"><UploadFilled /></el-icon>
-              <div>点击上传图片</div>
-            </div>
-          </el-upload>
-        </el-form-item>
-      </el-form>
-      <template #footer>
-        <el-button @click="dialogVisible = false">取消</el-button>
-        <el-button type="primary" @click="handleSave">确定</el-button>
-      </template>
-    </el-dialog>
-  </div>
-</template>
-
-<script setup>
-import { ref, reactive, onMounted } from 'vue'
-import { ElMessage, ElMessageBox } from 'element-plus'
-import { Plus, UploadFilled } from '@element-plus/icons-vue'
-import request from '@/utils/request'
-import { getEquipmentFullPage, addEquipment, updateEquipment, deleteEquipment, getEquipmentTypeList, getPipeNetworkOptions, getGasPipePointPage } from '@/api/pipeNetwork/basic'
-
-const baseApi = import.meta.env.VITE_APP_BASE_API
-
-const loading = ref(false)
-const tableData = ref([])
-const pageNum = ref(1)
-const pageSize = ref(10)
-const total = ref(0)
-const searchForm = reactive({ equipmentCode: '', equipmentName: '', equipmentModel: '', manufacturer: '', district: '' })
-const dialogVisible = ref(false)
-const dialogTitle = ref('新增')
-const formRef = ref(null)
-
-const rules = {
-  equipmentName: [{ required: true, message: '请输入设备名称', trigger: 'blur' }],
-  equipmentTypeId: [{ required: true, message: '请选择设备类别', trigger: 'change' }],
-  district: [{ required: true, message: '请选择所属片区', trigger: 'change' }]
-}
-
-const detailVisible = ref(false)
-const detailData = ref(null)
-const equipmentTypeOptions = ref([])
-const networkOptions = ref([])
-const pointOptions = ref([])
-const equipmentImagePreview = ref('')
-let equipmentImageFile = null
-
-const districtOptions = [
-  { label: '城南', value: '城南', prefix: 'CN' },
-  { label: '城北', value: '城北', prefix: 'CB' },
-  { label: '太常', value: '太常', prefix: 'TC' }
-]
-
-const generateEquipmentCode = () => {
-  if (!formData.district) return ''
-  const opt = districtOptions.find(o => o.value === formData.district)
-  if (opt && opt.prefix) {
-    const now = new Date()
-    const dateStr = `${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, '0')}${String(now.getDate()).padStart(2, '0')}`
-    const timeStr = `${String(now.getHours()).padStart(2, '0')}${String(now.getMinutes()).padStart(2, '0')}${String(now.getSeconds()).padStart(2, '0')}`
-    return `${opt.prefix}-GAS-${dateStr}${timeStr}`
-  }
-  return ''
-}
-
-const formData = reactive({
-  district: '',
-  equipmentCode: '',
-  equipmentName: '', equipmentModel: '',
-  equipmentSpec: '', manufacturer: '', equipmentLocation: '', equipmentTypeId: '',
-  networkId: '', pointId: '',
-  longitude: null, latitude: null, maintainer: '', maintainerPhone: '', remark: '',
-  equipmentImage: ''
-})
-
-async function loadData() {
-  loading.value = true
-  try {
-    const res = await getEquipmentFullPage(pageNum.value, pageSize.value, searchForm)
-    tableData.value = (res.data && res.data.records) ? res.data.records : []
-    total.value = (res.data && res.data.total) ? res.data.total : 0
-  } catch (e) { console.error(e) } finally { loading.value = false }
-}
-function handleSearch() { pageNum.value = 1; loadData() }
-function handleReset() {
-  Object.assign(searchForm, { equipmentCode: '', equipmentName: '', equipmentModel: '', manufacturer: '', district: '' })
-  handleSearch()
-}
-async function loadEquipmentTypes() {
-  if (equipmentTypeOptions.value.length > 0) return
-  try {
-    const res = await getEquipmentTypeList()
-    const all = res.data || []
-    equipmentTypeOptions.value = all.filter(t => Number(t.parentId) === 3)
-  } catch (e) { console.error(e) }
-}
-async function loadNetworkOptions() {
-  if (networkOptions.value.length > 0) return
-  try { const res = await getPipeNetworkOptions(); networkOptions.value = res.data || [] } catch (e) { console.error(e) }
-}
-async function loadPointOptions(networkId) {
-  try {
-    const params = networkId ? { networkId } : {}
-    const res = await getGasPipePointPage(1, 200, params)
-    pointOptions.value = (res.data && res.data.records) ? res.data.records : []
-  } catch (e) { console.error(e) }
-}
-function onNetworkChange(val) {
-  formData.pointId = ''
-  if (val) { loadPointOptions(val) } else { pointOptions.value = [] }
-}
-function onDistrictChange() {
-  // 当片区变更时,如果正在新增模式下,自动生成设备编码
-  if (!formData.equipmentId && formData.district) {
-    formData.equipmentCode = generateEquipmentCode()
-  }
-}
-/** 覆盖 el-upload 默认上传行为,不做任何请求 */
-function handleUploadRequest() {
-  // 空函数,阻止 el-upload 默认的 action 请求
-}
-
-/** 图片选择后处理(验证 + 预览) */
-function handleImageChange(file) {
-  if (!file.raw) return
-  const isImage = file.raw.type === 'image/jpeg' || file.raw.type === 'image/png' || file.raw.type === 'image/jpg'
-  if (!isImage) {
-    ElMessage.error('只能上传图片文件 (JPG/PNG)!')
-    return
-  }
-  const isLt5M = file.raw.size / 1024 / 1024 < 5
-  if (!isLt5M) {
-    ElMessage.error('图片大小不能超过 5MB!')
-    return
-  }
-  equipmentImageFile = file.raw
-  const reader = new FileReader()
-  reader.readAsDataURL(file.raw)
-  reader.onload = () => {
-    equipmentImagePreview.value = reader.result
-  }
-}
-
-/** 图片删除 */
-function handleImageRemove() {
-  equipmentImageFile = null
-  equipmentImagePreview.value = ''
-  formData.equipmentImage = ''
-}
-
-function handleAdd() {
-  dialogTitle.value = '新增'
-  const template = { 
-    equipmentCode: '', 
-    equipmentName: '', 
-    equipmentModel: '', 
-    equipmentSpec: '', 
-    manufacturer: '', 
-    equipmentLocation: '', 
-    equipmentTypeId: '', 
-    networkId: '', 
-    pointId: '', 
-    longitude: null, 
-    latitude: null, 
-    maintainer: '', 
-    maintainerPhone: '', 
-    remark: '',
-    district: '',
-    equipmentImage: ''
-  }
-  if (formData.equipmentId) delete formData.equipmentId
-  Object.assign(formData, template)
-  equipmentImageFile = null
-  equipmentImagePreview.value = ''
-  dialogVisible.value = true
-  loadEquipmentTypes(); loadNetworkOptions()
-}
-function handleEdit(row) {
-  dialogTitle.value = '编辑'
-  Object.assign(formData, row)
-  // 如果有已有图片,直接使用返回的 URL 生成预览
-  if (row.equipmentImage) {
-    equipmentImagePreview.value = row.equipmentImage
-  } else {
-    equipmentImagePreview.value = ''
-  }
-  equipmentImageFile = null
-  dialogVisible.value = true
-  loadEquipmentTypes(); loadNetworkOptions()
-  if (row.networkId) loadPointOptions(row.networkId)
-}
-async function handleSave() {
-  const valid = await formRef.value.validate().catch(() => false)
-  if (!valid) return
-  
-  try {
-    // 如果有新图片,先通过 MinIO 通用接口上传到 MinIO
-    if (equipmentImageFile) {
-      const uploadFormData = new FormData()
-      uploadFormData.append('file', equipmentImageFile)
-      uploadFormData.append('dir', 'upload') // 指定存储目录(可选)
-      const uploadResponse = await request({
-        url: '/minio/upload',
-        method: 'post',
-        data: uploadFormData
-      })
-      formData.equipmentImage = uploadResponse.url || uploadResponse.fileName
-      // 上传完成后清空文件,避免重复上传
-      equipmentImageFile = null
-    }
-    
-    const isEdit = !!formData.equipmentId
-    if (isEdit) {
-      await updateEquipment(formData)
-    } else {
-      const res = await addEquipment(formData)
-      formData.equipmentId = res.data // 后端 saveWithCheck 返回实体 UUID
-      if (!formData.equipmentId) {
-        ElMessage.error('新增失败,未获取到设备 ID')
-        return
-      }
-    }
-    ElMessage.success(isEdit ? '修改成功' : '新增成功')
-    dialogVisible.value = false
-    loadData()
-  } catch (e) {
-    const msg = e?.response?.data?.msg || e?.message || '操作失败'
-    ElMessage.error(msg)
-  }
-}
-function handleDelete(row) {
-  ElMessageBox.confirm('确认删除该设备信息?', '提示', { type: 'warning' }).then(async () => {
-    await deleteEquipment(row.equipmentId); ElMessage.success('删除成功'); loadData()
-  }).catch(() => {})
-}
-async function handleDetail(row) {
-  detailData.value = row
-  detailVisible.value = true
-}
-
-onMounted(() => { loadData() })
-</script>
-
-<style scoped>
-.equipment-container { padding: 16px; }
-.search-card { margin-bottom: 16px; }
-.search-form { display: flex; flex-wrap: wrap; }
-
-/* 表格图片样式 */
-.table-image-cell {
-  width: 80px;
-}
-
-.equipment-table-image {
-  width: 64px;
-  height: 64px;
-  border-radius: 4px;
-  cursor: pointer;
-  object-fit: cover;
-}
-
-/* 详情图片样式 */
-.detail-equipment-image {
-  width: 200px;
-  height: 200px;
-  border-radius: 6px;
-  cursor: pointer;
-  object-fit: cover;
-}
-
-/* 图片上传样式 */
-.image-uploader {
-  position: relative;
-  border: 1px dashed #d9d9d9;
-  border-radius: 6px;
-  cursor: pointer;
-  width: 200px;
-  height: 200px;
-}
-
-.image-uploader:hover {
-  border-color: #409eff;
-}
-
-.image-uploader .preview-image {
-  width: 100%;
-  height: 100%;
-  object-fit: cover;
-  display: block;
-  border-radius: 6px;
-}
-
-.image-uploader-placeholder {
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  justify-content: center;
-  height: 100%;
-  color: #909399;
-  font-size: 14px;
-}
-
-.image-uploader-placeholder .el-icon--upload {
-  font-size: 28px;
-  margin-bottom: 8px;
-}
-
-.image-uploader-cover {
-  position: absolute;
-  top: 0;
-  left: 0;
-  right: 0;
-  bottom: 0;
-  background: rgba(0, 0, 0, 0.5);
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  opacity: 0;
-  transition: opacity 0.3s;
-  border-radius: 6px;
-}
-
-.image-uploader:hover .image-uploader-cover {
-  opacity: 1;
-}
-
-.image-uploader-cover .el-icon {
-  color: #fff;
-  font-size: 28px;
-}
-</style>

+ 0 - 721
src/views/subSystem/drainage/jcsj/sbjcgl.vue

@@ -1,721 +0,0 @@
-<template>
-  <div class="app-container">
-    <!-- 搜索区域 -->
-    <el-form :model="queryParams" ref="queryRef" :inline="true" class="search-form">
-      <el-form-item label="设备名称">
-        <el-input v-model="queryParams.equipmentName" placeholder="请输入" clearable style="width: 200px" />
-      </el-form-item>
-      <el-form-item label="设备状态">
-        <el-select v-model="queryParams.currentStatus" placeholder="请选择" clearable style="width: 200px">
-          <el-option v-for="item in statusOptions" :key="item.value" :label="item.label" :value="item.value" />
-        </el-select>
-      </el-form-item>
-      <el-form-item>
-        <el-button type="primary" @click="handleQuery">搜索</el-button>
-      </el-form-item>
-    </el-form>
-
-    <!-- 操作按钮区域 -->
-    <div class="toolbar">
-      <el-button type="primary" @click="handleAdd">新增</el-button>
-      <el-button type="danger" :disabled="selectedIds.length === 0" @click="handleBatchDelete">删除</el-button>
-    </div>
-
-    <!-- 数据表格 -->
-    <el-table v-loading="loading" :data="tableData" border style="width: 100%" @selection-change="handleSelectionChange">
-      <el-table-column type="selection" width="55" align="center" />
-      <el-table-column label="设备名称" prop="equipmentName" min-width="140" />
-      <el-table-column label="设备编号" prop="equipmentCode" min-width="130" />
-      <el-table-column label="设备类型" prop="equipmentTypeName" min-width="90">
-        <template #default="{ row }">
-          <span>{{ row.equipmentTypeName || '-' }}</span>
-        </template>
-      </el-table-column>
-      <el-table-column label="安装位置" prop="equipmentLocation" min-width="150" />
-      <el-table-column label="设备状态" prop="currentStatus" min-width="90" align="center">
-        <template #default="{ row }">
-          <el-tag :type="statusTagType(row.currentStatus)">{{ statusText(row.currentStatus) }}</el-tag>
-        </template>
-      </el-table-column>
-      <el-table-column label="报警状态" prop="alarmStatus" min-width="90" align="center">
-        <template #default="{ row }">
-          <el-tag :type="row.alarmStatus === 1 ? 'danger' : 'success'">{{ row.alarmStatus === 1 ? '报警' : '正常' }}</el-tag>
-        </template>
-      </el-table-column>
-      <el-table-column label="在线状态" prop="onlineStatus" min-width="90" align="center">
-        <template #default="{ row }">
-          <el-tag :type="row.onlineStatus === 1 ? 'success' : 'info'">{{ row.onlineStatus === 1 ? '在线' : '离线' }}</el-tag>
-        </template>
-      </el-table-column>
-      <el-table-column label="安装时间" prop="installTime" min-width="120" />
-      <el-table-column label="最近维护时间" prop="lastMaintainDate" min-width="130" />
-      <el-table-column label="下次维护日期" prop="nextMaintainDate" min-width="130" />
-      <el-table-column label="操作" width="180" align="center" fixed="right">
-        <template #default="{ row }">
-          <el-button link type="primary" @click="handleView(row)">查看</el-button>
-          <el-button link type="primary" @click="handleEdit(row)">修改</el-button>
-          <el-button link type="danger" @click="handleDelete(row)">删除</el-button>
-        </template>
-      </el-table-column>
-    </el-table>
-
-    <!-- 分页 -->
-    <el-pagination
-      v-model:current-page="queryParams.pageNum"
-      v-model:page-size="queryParams.pageSize"
-      :page-sizes="[10, 20, 50, 100]"
-      :total="total"
-      layout="total, sizes, prev, pager, next, jumper"
-      @size-change="handleQuery"
-      @current-change="handlePageChange"
-    />
-
-    <!-- 查看详情对话框 -->
-    <el-drawer v-model="dialogVisible" title="设备详情" size="700px" :before-close="handleClose" class="detail-drawer" direction="rtl">
-      <div class="detail-info">
-        <el-row :gutter="20">
-          <el-col :span="12">
-            <div class="info-item">
-              <span class="info-label">设备名称:</span>
-              <span class="info-value">{{ currentRow.equipmentName }}</span>
-            </div>
-          </el-col>
-          <el-col :span="12">
-            <div class="info-item">
-              <span class="info-label">设备编号:</span>
-              <span class="info-value">{{ currentRow.equipmentCode }}</span>
-            </div>
-          </el-col>
-        </el-row>
-        <el-row :gutter="20">
-          <el-col :span="12">
-            <div class="info-item">
-              <span class="info-label">设备类型:</span>
-              <span class="info-value">{{ currentRow.equipmentTypeName || '-' }}</span>
-            </div>
-          </el-col>
-          <el-col :span="12">
-            <div class="info-item">
-              <span class="info-label">权属单位:</span>
-              <span class="info-value">{{ currentRow.ownershipUnit || '-' }}</span>
-            </div>
-          </el-col>
-        </el-row>
-        <el-row :gutter="20">
-          <el-col :span="12">
-            <div class="info-item">
-              <span class="info-label">安装位置:</span>
-              <span class="info-value">{{ currentRow.equipmentLocation }}</span>
-            </div>
-          </el-col>
-          <el-col :span="12">
-            <div class="info-item">
-              <span class="info-label">设备状态:</span>
-              <span class="info-value">
-                <el-tag :type="statusTagType(currentRow.currentStatus)">{{ statusText(currentRow.currentStatus) }}</el-tag>
-              </span>
-            </div>
-          </el-col>
-        </el-row>
-        <el-row :gutter="20">
-          <el-col :span="12">
-            <div class="info-item">
-              <span class="info-label">安装时间:</span>
-              <span class="info-value">{{ currentRow.installTime }}</span>
-            </div>
-          </el-col>
-          <el-col :span="12">
-            <div class="info-item">
-              <span class="info-label">最近维护时间:</span>
-              <span class="info-value">{{ currentRow.lastMaintainDate }}</span>
-            </div>
-          </el-col>
-        </el-row>
-        <el-row :gutter="20">
-          <el-col :span="24">
-            <div class="info-item">
-              <span class="info-label">备注:</span>
-              <span class="info-value">{{ currentRow.remark || '-' }}</span>
-            </div>
-          </el-col>
-        </el-row>
-      </div>
-    </el-drawer>
-
-    <!-- 新增/修改对话框 -->
-    <el-dialog v-model="editDialogVisible" :title="editDialogTitle" width="800px" :before-close="handleEditClose">
-      <el-form ref="editFormRef" :model="editForm" :rules="editRules" label-width="120px">
-        <el-row :gutter="20">
-          <el-col :span="12">
-            <el-form-item label="设备名称" prop="equipmentName">
-              <el-input v-model="editForm.equipmentName" placeholder="请输入设备名称" />
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="设备编号" prop="equipmentCode">
-              <el-input v-model="editForm.equipmentCode" placeholder="请输入设备编号" />
-            </el-form-item>
-          </el-col>
-        </el-row>
-        <el-row :gutter="20">
-          <el-col :span="12">
-            <el-form-item label="设备型号" prop="equipmentModel">
-              <el-input v-model="editForm.equipmentModel" placeholder="请输入设备型号" />
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="设备类型" prop="equipmentTypeId">
-              <el-select v-model="editForm.equipmentTypeId" placeholder="请选择设备类型" style="width: 100%">
-                <el-option v-for="item in drainageTypeOptions" :key="item.value" :label="item.label" :value="item.value" />
-              </el-select>
-            </el-form-item>
-          </el-col>
-        </el-row>
-        <el-row :gutter="20">
-          <el-col :span="12">
-            <el-form-item label="设备制造商" prop="manufacturer">
-              <el-input v-model="editForm.manufacturer" placeholder="请输入设备制造商" />
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="生产日期" prop="productionDate">
-              <el-date-picker v-model="editForm.productionDate" type="date" placeholder="请选择生产日期" value-format="YYYY-MM-DD" style="width: 100%" />
-            </el-form-item>
-          </el-col>
-        </el-row>
-        <el-row :gutter="20">
-          <el-col :span="24">
-            <el-form-item label="设备规格参数" prop="equipmentSpec">
-              <el-input v-model="editForm.equipmentSpec" type="textarea" :rows="2" placeholder="请输入设备规格参数" />
-            </el-form-item>
-          </el-col>
-        </el-row>
-        <el-row :gutter="20">
-          <el-col :span="12">
-            <el-form-item label="资产价值(元)" prop="assetValue">
-              <el-input-number v-model="editForm.assetValue" :precision="2" :min="0" placeholder="请输入" style="width: 100%" />
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="使用年限(年)" prop="useLife">
-              <el-input-number v-model="editForm.useLife" :min="0" :precision="0" placeholder="请输入" style="width: 100%" />
-            </el-form-item>
-          </el-col>
-        </el-row>
-        <el-row :gutter="20">
-          <el-col :span="12">
-            <el-form-item label="经度" prop="longitude">
-              <el-input-number v-model="editForm.longitude" :precision="8" :min="-180" :max="180" :controls="false" placeholder="请输入经度" style="width: 100%" />
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="纬度" prop="latitude">
-              <el-input-number v-model="editForm.latitude" :precision="8" :min="-90" :max="90" :controls="false" placeholder="请输入纬度" style="width: 100%" />
-            </el-form-item>
-          </el-col>
-        </el-row>
-        <el-row :gutter="20">
-          <el-col :span="12">
-            <el-form-item label="安装位置" prop="equipmentLocation">
-              <el-input v-model="editForm.equipmentLocation" placeholder="请输入安装位置" />
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="安装时间" prop="installTime">
-              <el-date-picker v-model="editForm.installTime" type="date" placeholder="请选择安装时间" value-format="YYYY-MM-DD" style="width: 100%" />
-            </el-form-item>
-          </el-col>
-        </el-row>
-        <el-row :gutter="20">
-          <el-col :span="12">
-            <el-form-item label="维护人员" prop="maintainer">
-              <el-input v-model="editForm.maintainer" placeholder="请输入维护人员" />
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="联系方式" prop="maintainerPhone">
-              <el-input v-model="editForm.maintainerPhone" placeholder="请输入联系方式" />
-            </el-form-item>
-          </el-col>
-        </el-row>
-        <el-row :gutter="20">
-          <el-col :span="12">
-            <el-form-item label="权属单位" prop="ownershipUnit">
-              <el-input v-model="editForm.ownershipUnit" placeholder="请输入权属单位" />
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="设备状态" prop="status">
-              <el-select v-model="editForm.status" placeholder="请选择设备状态" style="width: 100%">
-                <el-option v-for="item in statusOptions" :key="item.value" :label="item.label" :value="item.value" />
-              </el-select>
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="报警状态" prop="alarmStatus">
-              <el-select v-model="editForm.alarmStatus" placeholder="请选择报警状态" style="width: 100%">
-                <el-option label="正常" :value="0" />
-                <el-option label="报警" :value="1" />
-              </el-select>
-            </el-form-item>
-          </el-col>
-        </el-row>
-        <el-row :gutter="20">
-          <el-col :span="12">
-            <el-form-item label="在线状态" prop="onlineStatus">
-              <el-select v-model="editForm.onlineStatus" placeholder="请选择在线状态" style="width: 100%">
-                <el-option label="离线" :value="0" />
-                <el-option label="在线" :value="1" />
-              </el-select>
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="最近维护时间" prop="lastMaintenanceTime">
-              <el-date-picker v-model="editForm.lastMaintenanceTime" type="date" placeholder="请选择维护时间" value-format="YYYY-MM-DD" style="width: 100%" />
-            </el-form-item>
-          </el-col>
-        </el-row>
-        <el-row :gutter="20">
-          <el-col :span="12">
-            <el-form-item label="下次维护日期" prop="nextMaintenanceTime">
-              <el-date-picker v-model="editForm.nextMaintenanceTime" type="date" placeholder="请选择下次维护日期" value-format="YYYY-MM-DD" style="width: 100%" />
-            </el-form-item>
-          </el-col>
-        </el-row>
-        <el-row :gutter="20">
-          <el-col :span="24">
-            <el-form-item label="备注" prop="remark">
-              <el-input v-model="editForm.remark" type="textarea" :rows="3" placeholder="请输入备注" />
-            </el-form-item>
-          </el-col>
-        </el-row>
-      </el-form>
-      <template #footer>
-        <el-button @click="handleEditClose">取 消</el-button>
-        <el-button type="primary" @click="handleEditSubmit">确 定</el-button>
-      </template>
-    </el-dialog>
-  </div>
-</template>
-
-<script setup name="Sbjcgl">
-import { ref, computed, onMounted } from 'vue'
-import { ElMessage, ElMessageBox } from 'element-plus'
-import {
-  getEquipmentPage,
-  getEquipmentById,
-  addEquipment,
-  updateEquipment,
-  deleteEquipment,
-  batchDeleteEquipment,
-  getEquipmentTypeList,
-  addEquipmentStatus,
-  updateEquipmentStatus
-} from '@/api/drainage'
-
-// ==================== 选项配置 ====================
-const typeList = ref([])
-const typeOptions = computed(() => {
-  return typeList.value.map(item => ({ label: item.typeName, value: item.id }))
-})
-
-const drainageTypeOptions = computed(() => {
-  // 找到排水父类记录(主键 id=2 且 parentId 为 0)
-  const drainageParent = typeList.value.find(item => Number(item.id) === 2 && String(item.parentId) === '0')
-  if (!drainageParent) {
-    // 如果找不到父类,返回所有非顶级的类型
-    return typeList.value.filter(item => item.parentId !== null && String(item.parentId) !== '0').map(item => ({ label: item.typeName, value: item.id }))
-  }
-  // 返回parentId等于drainage父类id的子类型
-  return typeList.value.filter(item => String(item.parentId) === String(drainageParent.id)).map(item => ({ label: item.typeName, value: item.id }))
-})
-
-const statusOptions = [
-  { label: '在用', value: 1 },
-  { label: '闲置', value: 2 },
-  { label: '维修', value: 3 },
-  { label: '报废', value: 4 },
-  { label: '待入库', value: 5 }
-]
-
-function typeText(val) {
-  const item = typeList.value.find(t => String(t.id) === String(val))
-  return item ? item.typeName : '-'
-}
-
-function statusText(val) {
-  const item = statusOptions.find(s => s.value === val)
-  return item ? item.label : '-'
-}
-
-function statusTagType(val) {
-  const map = { 1: 'success', 2: 'info', 3: 'warning', 4: 'danger', 5: '' }
-  return map[val] || 'info'
-}
-
-// ==================== 搜索参数 ====================
-const queryParams = ref({
-  equipmentName: '',
-  equipmentTypeId: '',
-  currentStatus: '',
-  pageNum: 1,
-  pageSize: 10
-})
-
-// ==================== 表格数据 ====================
-const tableData = ref([])
-const total = ref(0)
-const loading = ref(false)
-
-// 多选
-const selectedIds = ref([])
-function handleSelectionChange(selection) {
-  selectedIds.value = selection.map(item => item.equipmentId)
-}
-
-// 加载分页数据
-async function loadData() {
-  loading.value = true
-  try {
-    const { pageNum, pageSize, ...rest } = queryParams.value
-    const res = await getEquipmentPage(pageNum, pageSize, rest)
-    const pageData = res.code !== undefined ? res.data : res
-    tableData.value = pageData.records || []
-    total.value = pageData.total || 0
-  } catch (error) {
-    console.error('加载设备列表失败', error)
-    ElMessage.error('加载设备列表失败')
-  } finally {
-    loading.value = false
-  }
-}
-
-// 搜索
-function handleQuery() {
-  queryParams.value.pageNum = 1
-  loadData()
-}
-
-function handlePageChange() {
-  loadData()
-}
-
-onMounted(async () => {
-  await loadTypeList()
-  loadData()
-})
-
-async function loadTypeList() {
-  try {
-    const res = await getEquipmentTypeList()
-    typeList.value = (res.code !== undefined ? res.data : res) || []
-    // 分页接口要求传 equipment_type.id(2=排水设备)
-    const drainageParent = typeList.value.find(item =>
-      Number(item.id) === 2 && String(item.parentId) === '0'
-    )
-    queryParams.value.equipmentTypeId = drainageParent ? String(drainageParent.id) : ''
-  } catch (error) {
-    console.error('加载设备类型失败', error)
-    ElMessage.error('加载设备类型失败')
-  }
-}
-
-// ==================== 查看详情对话框 ====================
-const dialogVisible = ref(false)
-const currentRow = ref({})
-
-async function handleView(row) {
-  try {
-    const res = await getEquipmentById(row.equipmentId)
-    const detail = res.data || {}
-    currentRow.value = {
-      ...row,
-      ...detail.equipmentInfo,
-      ...detail.statusInfo,
-      equipmentTypeName: detail.typeInfo ? detail.typeInfo.typeName : (row.equipmentTypeName || '-')
-    }
-    dialogVisible.value = true
-  } catch (error) {
-    console.error('获取设备详情失败', error)
-    ElMessage.error('获取设备详情失败')
-  }
-}
-
-function handleClose() {
-  dialogVisible.value = false
-}
-
-// ==================== 新增/修改对话框 ====================
-const editDialogVisible = ref(false)
-const editDialogTitle = ref('新增设备')
-const editFormRef = ref(null)
-const isEditMode = ref(false)
-const editRowId = ref(null)
-
-const editForm = ref({
-  equipmentName: '',
-  equipmentCode: '',
-  equipmentModel: '',
-  equipmentSpec: '',
-  manufacturer: '',
-  productionDate: '',
-  equipmentTypeId: '',
-  assetValue: null,
-  useLife: null,
-  longitude: null,
-  latitude: null,
-  equipmentLocation: '',
-  installTime: '',
-  maintainer: '',
-  maintainerPhone: '',
-  ownershipUnit: '',
-  remark: '',
-  // 状态表字段
-  status: '',
-  alarmStatus: null,
-  onlineStatus: null,
-  lastMaintenanceTime: '',
-  nextMaintenanceTime: ''
-})
-
-const editRules = {
-  equipmentName: [{ required: true, message: '请输入设备名称', trigger: 'blur' }],
-  equipmentCode: [{ required: true, message: '请输入设备编号', trigger: 'blur' }],
-  equipmentTypeId: [{ required: true, message: '请选择设备类型', trigger: 'change' }],
-  equipmentLocation: [{ required: true, message: '请输入安装位置', trigger: 'blur' }],
-  status: [{ required: true, message: '请选择设备状态', trigger: 'change' }]
-}
-
-function handleAdd() {
-  isEditMode.value = false
-  editDialogTitle.value = '新增设备'
-  editRowId.value = null
-  editForm.value = {
-    equipmentName: '',
-    equipmentCode: '',
-    equipmentModel: '',
-    equipmentSpec: '',
-    manufacturer: '',
-    productionDate: '',
-    equipmentTypeId: '',
-    assetValue: null,
-    useLife: null,
-    longitude: null,
-    latitude: null,
-    equipmentLocation: '',
-    installTime: '',
-    maintainer: '',
-    maintainerPhone: '',
-    ownershipUnit: '',
-    remark: '',
-    status: '',
-    alarmStatus: null,
-    onlineStatus: null,
-    lastMaintenanceTime: '',
-    nextMaintenanceTime: ''
-  }
-  editDialogVisible.value = true
-}
-
-function handleEdit(row) {
-  isEditMode.value = true
-  editDialogTitle.value = '修改设备'
-  editRowId.value = row.equipmentId
-  editForm.value = {
-    equipmentName: row.equipmentName || '',
-    equipmentCode: row.equipmentCode || '',
-    equipmentModel: row.equipmentModel || '',
-    equipmentSpec: row.equipmentSpec || '',
-    manufacturer: row.manufacturer || '',
-    productionDate: row.productionDate || '',
-    equipmentTypeId: row.equipmentTypeId || '',
-    assetValue: row.assetValue || null,
-    useLife: row.useLife || null,
-    longitude: row.longitude || null,
-    latitude: row.latitude || null,
-    equipmentLocation: row.equipmentLocation || '',
-    installTime: row.installTime ? row.installTime.split(' ')[0] : '',
-    maintainer: row.maintainer || '',
-    maintainerPhone: row.maintainerPhone || '',
-    ownershipUnit: row.ownershipUnit || '',
-    remark: row.remark || '',
-    status: row.currentStatus || '',
-    alarmStatus: row.alarmStatus != null ? row.alarmStatus : null,
-    onlineStatus: row.onlineStatus != null ? row.onlineStatus : null,
-    lastMaintenanceTime: row.lastMaintainDate || '',
-    nextMaintenanceTime: row.nextMaintainDate || ''
-  }
-  editDialogVisible.value = true
-}
-
-function handleEditClose() {
-  editDialogVisible.value = false
-}
-
-function handleEditSubmit() {
-  editFormRef.value.validate(async valid => {
-    if (!valid) return
-
-    const formData = {
-      equipmentName: editForm.value.equipmentName,
-      equipmentCode: editForm.value.equipmentCode,
-      equipmentModel: editForm.value.equipmentModel,
-      equipmentSpec: editForm.value.equipmentSpec,
-      manufacturer: editForm.value.manufacturer,
-      productionDate: editForm.value.productionDate || null,
-      equipmentTypeId: editForm.value.equipmentTypeId,
-      equipmentType: 'drainage',
-      assetValue: editForm.value.assetValue,
-      useLife: editForm.value.useLife,
-      longitude: editForm.value.longitude,
-      latitude: editForm.value.latitude,
-      equipmentLocation: editForm.value.equipmentLocation,
-      installTime: editForm.value.installTime ? editForm.value.installTime + ' 00:00:00' : null,
-      maintainer: editForm.value.maintainer,
-      maintainerPhone: editForm.value.maintainerPhone,
-      ownershipUnit: editForm.value.ownershipUnit,
-      remark: editForm.value.remark
-    }
-
-    try {
-      if (isEditMode.value) {
-        formData.equipmentId = editRowId.value
-        const res = await updateEquipment(formData)
-        if (res.code === 0 || res.code === 200) {
-          // 更新设备状态
-          if (editForm.value.status) {
-            try {
-              await updateEquipmentStatus({
-                equipmentId: editRowId.value,
-                currentStatus: editForm.value.status,
-                alarmStatus: editForm.value.alarmStatus,
-                onlineStatus: editForm.value.onlineStatus,
-                installationAddr: editForm.value.equipmentLocation,
-                lastMaintainDate: editForm.value.lastMaintenanceTime || null,
-                nextMaintainDate: editForm.value.nextMaintenanceTime || null
-              })
-            } catch (e) {
-              console.warn('更新设备状态失败', e)
-            }
-          }
-          ElMessage.success('修改成功')
-        } else {
-          ElMessage.error(res.msg || '修改失败')
-          return
-        }
-      } else {
-        const res = await addEquipment(formData)
-        if (res.code === 0 || res.code === 200) {
-          // 新增成功后,创建设备状态记录
-          const newEquipmentId = res.data ? (res.data.equipmentId || null) : null
-          if (newEquipmentId && editForm.value.status) {
-            try {
-              await addEquipmentStatus({
-                equipmentId: newEquipmentId,
-                currentStatus: editForm.value.status,
-                alarmStatus: editForm.value.alarmStatus,
-                onlineStatus: editForm.value.onlineStatus,
-                installationAddr: editForm.value.equipmentLocation,
-                lastMaintainDate: editForm.value.lastMaintenanceTime || null,
-                nextMaintainDate: editForm.value.nextMaintenanceTime || null
-              })
-            } catch (e) {
-              console.warn('创建设备状态失败', e)
-            }
-          }
-          ElMessage.success('新增成功')
-          // 新增记录按创建时间倒序显示,回到第一页后才能立即看到。
-          queryParams.value.pageNum = 1
-        } else {
-          ElMessage.error(res.msg || '新增失败')
-          return
-        }
-      }
-      editDialogVisible.value = false
-      loadData()
-    } catch (error) {
-      console.error('提交失败', error)
-      ElMessage.error('操作失败')
-    }
-  })
-}
-
-// ==================== 删除功能 ====================
-function handleDelete(row) {
-  ElMessageBox.confirm(`确认删除设备"${row.equipmentName}"吗?`, '提示', {
-    confirmButtonText: '确定',
-    cancelButtonText: '取消',
-    type: 'warning'
-  }).then(async () => {
-    try {
-      const res = await deleteEquipment(row.equipmentId)
-      if (res.code === 0 || res.code === 200) {
-        ElMessage.success('删除成功')
-        loadData()
-      } else {
-        ElMessage.error(res.msg || '删除失败')
-      }
-    } catch (error) {
-      ElMessage.error('删除失败')
-    }
-  }).catch(() => { })
-}
-
-function handleBatchDelete() {
-  if (selectedIds.value.length === 0) return
-  ElMessageBox.confirm(`确认删除选中的 ${selectedIds.value.length} 条数据吗?`, '提示', {
-    confirmButtonText: '确定',
-    cancelButtonText: '取消',
-    type: 'warning'
-  }).then(async () => {
-    try {
-      const res = await batchDeleteEquipment(selectedIds.value)
-      if (res.code === 0 || res.code === 200) {
-        selectedIds.value = []
-        ElMessage.success('删除成功')
-        loadData()
-      } else {
-        ElMessage.error(res.msg || '删除失败')
-      }
-    } catch (error) {
-      ElMessage.error('删除失败')
-    }
-  }).catch(() => { })
-}
-</script>
-
-<style scoped>
-.app-container {
-  padding: 20px;
-}
-
-.search-form {
-  margin-bottom: 16px;
-}
-
-.toolbar {
-  margin-bottom: 16px;
-}
-
-.detail-info {
-  margin-bottom: 16px;
-}
-
-.info-item {
-  display: flex;
-  align-items: center;
-  margin-bottom: 12px;
-  line-height: 32px;
-}
-
-.info-label {
-  color: #909399;
-  font-size: 14px;
-  white-space: nowrap;
-}
-
-.info-value {
-  color: #303133;
-  font-size: 14px;
-}
-</style>

文件差異過大導致無法顯示
+ 0 - 0
src/views/subSystem/lifeCompany/warningThreshold/index.vue


+ 0 - 586
src/views/subSystem/manholeCover/device/index.vue

@@ -1,586 +0,0 @@
-<!--监测设备台账(窨井设备)-->
-<template>
-  <div class="manhole-cover-management">
-    <!-- 页面头部 -->
-    <div class="page-header">
-      <div class="stats-section">
-        <el-card class="stat-card" shadow="hover">
-          <div class="stat-content">
-            <el-icon><Monitor /></el-icon>
-            <div>
-              <div class="stat-label">在线设备</div>
-              <div class="stat-number">{{ onlineDevicesCount }}</div>
-            </div>
-          </div>
-        </el-card>
-        <el-card class="stat-card" shadow="hover">
-          <div class="stat-content">
-            <el-icon><CircleClose /></el-icon>
-            <div>
-              <div class="stat-label">离线设备</div>
-              <div class="stat-number">{{ offlineDevicesCount }}</div>
-            </div>
-          </div>
-        </el-card>
-        <el-card class="stat-card" shadow="hover">
-          <div class="stat-content">
-            <el-icon><Location /></el-icon>
-            <div>
-              <div class="stat-label">设备总数</div>
-              <div class="stat-number">{{ totalDevicesCount }}</div>
-            </div>
-          </div>
-        </el-card>
-      </div>
-    </div>
-
-    <!-- 筛选工具栏 -->
-    <div class="filter-bar">
-      <el-input
-        v-model="searchKeyword"
-        placeholder="搜索设备名称"
-        clearable
-        style="width: 240px"
-        :prefix-icon="Search"
-        @keyup.enter="handleSearch"
-        @clear="handleSearch"
-      />
-      <el-select v-model="typeFilter" placeholder="设备类型" clearable style="width: 160px" @change="handleSearch">
-        <el-option v-for="t in typeOptions" :key="t.value" :label="t.label" :value="t.value" />
-      </el-select>
-      <el-button type="primary" :icon="Refresh" @click="resetFilters">重置筛选</el-button>
-    </div>
-
-    <!-- 设备列表展示区 -->
-    <div class="device-list-container">
-      <el-table
-        :data="devices"
-        v-loading="loading"
-        stripe
-        border
-        style="width: 100%"
-        :row-class-name="tableRowClassName"
-        @row-click="handleRowClick"
-      >
-        <el-table-column prop="deviceCode" label="设备编号" width="150" show-overflow-tooltip />
-        <el-table-column prop="deviceName" label="设备名称" width="180" show-overflow-tooltip />
-        <el-table-column prop="typeName" label="设备类型" width="130">
-          <template #default="{ row }">
-            <el-tag size="small" effect="plain">{{ getTypeText(row) }}</el-tag>
-          </template>
-        </el-table-column>
-        <el-table-column prop="location" label="设备位置" min-width="200" show-overflow-tooltip />
-        <el-table-column prop="lastMaintainDate" label="最近运维时间" width="130">
-          <template #default="{ row }">{{ row.lastMaintainDate || '—' }}</template>
-        </el-table-column>
-        <el-table-column prop="onlineStatus" label="在线状态" width="110" align="center">
-          <template #default="{ row }">
-            <el-tag :type="getOnlineTagType(row.onlineStatus)" size="small">{{ getOnlineText(row.onlineStatus) }}</el-tag>
-          </template>
-        </el-table-column>
-        <el-table-column label="操作" width="180" fixed="right">
-          <template #default="{ row }">
-            <el-button link type="primary" size="small" @click.stop="showDetail(row)">详情</el-button>
-            <el-button link type="success" size="small" @click.stop="openRelDialog(row)">关联井下设备</el-button>
-          </template>
-        </el-table-column>
-      </el-table>
-
-      <!-- 分页 -->
-      <div class="pagination-wrapper">
-        <el-pagination
-          background
-          layout="total, prev, pager, next"
-          :total="total"
-          :page-size="pageSize"
-          v-model:current-page="currentPage"
-          @current-change="loadDevices"
-        />
-      </div>
-    </div>
-
-    <!-- 设备详情抽屉 -->
-    <el-drawer v-model="drawerVisible" title="设备电子档案 - 详细信息" size="40%" direction="rtl" v-loading="detailLoading" class="detail-drawer">
-      <template v-if="selectedDevice">
-        <el-descriptions :column="1" border>
-          <el-descriptions-item label="设备编号">{{ selectedDevice.deviceCode }}</el-descriptions-item>
-          <el-descriptions-item label="设备名称">{{ selectedDevice.deviceName }}</el-descriptions-item>
-          <el-descriptions-item label="设备类型">{{ getTypeText(selectedDevice) }}</el-descriptions-item>
-          <el-descriptions-item label="在线状态">
-            <el-tag :type="getOnlineTagType(selectedDevice.onlineStatus)">{{ getOnlineText(selectedDevice.onlineStatus) }}</el-tag>
-          </el-descriptions-item>
-          <el-descriptions-item label="设备位置">{{ selectedDevice.location || '—' }}</el-descriptions-item>
-          <el-descriptions-item label="最近运维时间">{{ selectedDevice.lastMaintainDate || '—' }}</el-descriptions-item>
-          <el-descriptions-item label="电池电量/健康度" v-if="selectedDevice.batteryLevel !== null && selectedDevice.batteryLevel !== undefined && selectedDevice.batteryLevel !== ''">{{ selectedDevice.batteryLevel }}%</el-descriptions-item>
-          <el-descriptions-item label="最后通讯时间">{{ selectedDevice.lastCommunication || '—' }}</el-descriptions-item>
-        </el-descriptions>
-
-        <el-divider content-position="left">动态监测指标</el-divider>
-        <div class="monitor-metrics">
-          <el-row :gutter="12">
-            <el-col :span="12"><el-statistic title="井盖倾斜角" :value="selectedDevice.metrics?.tilt ?? '—'" suffix="°" /></el-col>
-            <el-col :span="12"><el-statistic title="水浸状态" :value="selectedDevice.metrics?.waterIntrusion ? '已浸水' : '正常'" /></el-col>
-            <el-col :span="12"><el-statistic title="温度" :value="selectedDevice.metrics?.temperature ?? '—'" suffix="℃" /></el-col>
-            <el-col :span="12"><el-statistic title="信号量" :value="selectedDevice.metrics?.signal ?? '—'" /></el-col>
-          </el-row>
-        </div>
-
-        <el-divider content-position="left">预警阈值</el-divider>
-        <el-table v-if="selectedDevice.warningThresholds.length" :data="selectedDevice.warningThresholds" size="small" border>
-          <el-table-column prop="warningType" label="预警类型" width="100" />
-          <el-table-column prop="warningCode" label="预警编码" width="120" />
-          <el-table-column prop="minValue" label="最小值" width="90" />
-          <el-table-column prop="maxValue" label="最大值" width="90" />
-          <el-table-column prop="remark" label="备注" show-overflow-tooltip />
-        </el-table>
-        <div v-else style="color:#909399;font-size:13px;">暂无预警阈值配置</div>
-
-        <div style="margin-top: 20px; text-align: right;">
-          <el-button type="primary" @click="closeDrawer">关闭</el-button>
-        </div>
-      </template>
-    </el-drawer>
-
-    <!-- 关联设备管理对话框 -->
-    <el-dialog v-model="relDialogVisible" :title="`井下设备关联 - ${relDevice?.deviceName || ''}`" width="860px" @close="onRelDialogClose">
-      <div class="rel-container">
-        <!-- 已关联设备列表 -->
-        <div class="rel-section">
-          <div class="rel-section-title">
-            <span>已关联设备</span>
-            <el-tag type="info" size="small">{{ relList.length }} 台</el-tag>
-          </div>
-          <el-table :data="relList" border size="small" v-loading="relLoading" max-height="240">
-            <el-table-column prop="equipmentCode" label="设备编号" min-width="140" show-overflow-tooltip />
-            <el-table-column prop="equipmentName" label="设备名称" min-width="140" show-overflow-tooltip />
-            <el-table-column prop="equipmentLocation" label="位置" min-width="160" show-overflow-tooltip />
-            <el-table-column label="操作" width="90" align="center">
-              <template #default="{ row }">
-                <el-button link type="danger" size="small" @click="removeRel(row)">取消关联</el-button>
-              </template>
-            </el-table-column>
-          </el-table>
-        </div>
-
-        <!-- 搜索可选设备 -->
-        <div class="rel-section">
-          <div class="rel-section-title">
-            <span>添加关联设备</span>
-            <el-input
-              v-model="relSearchKeyword"
-              placeholder="搜索设备编号/名称/位置"
-              clearable
-              size="small"
-              style="width: 260px"
-              :prefix-icon="Search"
-              @keyup.enter="searchRelDevices"
-              @clear="searchRelDevices"
-            />
-          </div>
-          <el-table :data="searchResults" border size="small" v-loading="relSearching" max-height="240" @selection-change="handleSearchSelectionChange">
-            <el-table-column type="selection" width="45" />
-            <el-table-column prop="equipmentCode" label="设备编号" min-width="140" show-overflow-tooltip />
-            <el-table-column prop="equipmentName" label="设备名称" min-width="140" show-overflow-tooltip />
-            <el-table-column prop="equipmentLocation" label="位置" min-width="160" show-overflow-tooltip />
-            <el-table-column label="类型" width="120">
-              <template #default="{ row }">
-                <el-tag size="small" effect="plain">{{ getTypeText(row) }}</el-tag>
-              </template>
-            </el-table-column>
-          </el-table>
-          <div class="rel-search-footer">
-            <el-button type="primary" size="small" :disabled="!searchSelection.length" :loading="relSaving" @click="batchAddRel">关联选中设备 ({{ searchSelection.length }})</el-button>
-          </div>
-        </div>
-      </div>
-    </el-dialog>
-  </div>
-</template>
-
-<script setup>
-import { ref, onMounted } from 'vue'
-import { Monitor, CircleClose, Location, Search, Refresh } from '@element-plus/icons-vue'
-import { getManholeDevicePage, getManholeDeviceById, getEquipmentTypeList, searchManholeRelDevices, getManholeDeviceRels, addManholeDeviceRel, deleteManholeDeviceRel } from '@/api/pipeNetwork/basic'
-import { ElMessage } from 'element-plus'
-
-// 窨井设备类别:供水窨井(type_id=5)/ 排水窨井(type_id=9)/ 燃气窨井(type_id=10)
-const MANHOLE_CATEGORY_MAP = { '5': '供水窨井', '9': '排水窨井', '10': '燃气窨井' }
-
-// 设备列表 & 分页
-const devices = ref([])
-const loading = ref(false)
-const total = ref(0)
-const currentPage = ref(1)
-const pageSize = 10
-
-// 筛选条件
-const searchKeyword = ref('')
-const typeFilter = ref('')
-// 窨井类别选项(value=窨井类别ID:供水窨井5/排水窨井9/燃气窨井10)
-const typeOptions = ref([
-  { value: '5', label: '供水窨井' },
-  { value: '9', label: '排水窨井' },
-  { value: '10', label: '燃气窨井' }
-])
-// 全部类型:id -> { typeId, typeName, parentTypeId }
-const typeIdMap = ref({})
-
-// 统计
-const onlineDevicesCount = ref(0)
-const offlineDevicesCount = ref(0)
-const totalDevicesCount = ref(0)
-
-// 加载全部设备类型(用于类型列归属显示)
-async function loadTypeOptions() {
-  try {
-    const res = await getEquipmentTypeList()
-    const all = res.data || []
-    all.forEach(t => { typeIdMap.value[t.id] = t })
-  } catch (e) { /* 类型接口异常时,类型列回退显示 typeName */ }
-}
-
-// 设备类型显示文本(按所属窨井类别显示)
-const getTypeText = (row) => {
-  const t = typeIdMap.value[row.equipmentTypeId]
-  if (!t) return row.typeName || '—'
-  return MANHOLE_CATEGORY_MAP[String(t.parentId)] || MANHOLE_CATEGORY_MAP[row.equipmentTypeId] || t.typeName || '—'
-}
-
-// 加载设备列表(后端已按窨井设备类型过滤)
-async function loadDevices() {
-  loading.value = true
-  try {
-    const res = await getManholeDevicePage({
-      pageNum: currentPage.value,
-      pageSize,
-      deviceName: searchKeyword.value || null,
-      equipmentTypeId: typeFilter.value || null,
-      isQueryManholeData: true
-    })
-    const rows = res.data?.records || []
-    devices.value = rows.map(d => ({
-      equipmentId: d.equipmentId,
-      equipmentTypeId: d.equipmentTypeId,
-      deviceCode: d.equipmentCode,
-      deviceName: d.equipmentName,
-      typeName: d.equipmentTypeName,
-      location: d.equipmentLocation,
-      onlineStatus: d.equipmentStatus?.onlineStatus,
-      lastMaintainDate: d.equipmentStatus?.lastMaintainDate,
-      batteryLevel: d.manholeData?.batteryLevel,
-      lastCommunication: d.manholeData?.uploadTime || d.manholeData?.createTime,
-      metrics: d.manholeData ? {
-        tilt: d.manholeData.tiltAngle,
-        waterIntrusion: Number(d.manholeData.waterInfiltrationAlarmStatus) === 1,
-        temperature: d.manholeData.temperatureValue,
-        signal: d.manholeData.signalStrength
-      } : null
-    }))
-    total.value = res.data?.total || 0
-  } catch (e) {
-    ElMessage.error('设备列表加载失败')
-  } finally {
-    loading.value = false
-  }
-}
-
-// 统计(基于当前筛选条件下的全量设备)
-async function loadStats() {
-  try {
-    const res = await getManholeDevicePage({
-      pageNum: 1,
-      pageSize: 1000,
-      deviceName: searchKeyword.value || null,
-      equipmentTypeId: typeFilter.value || null,
-      isQueryManholeData: false
-    })
-    const rows = res.data?.records || []
-    totalDevicesCount.value = res.data?.total || rows.length
-    onlineDevicesCount.value = rows.filter(d => Number(d.equipmentStatus?.onlineStatus) === 1).length
-    offlineDevicesCount.value = rows.length - onlineDevicesCount.value
-  } catch (e) {
-    onlineDevicesCount.value = 0
-    offlineDevicesCount.value = 0
-    totalDevicesCount.value = 0
-  }
-}
-
-// 在线状态
-const getOnlineText = (status) => {
-  return Number(status) === 1 ? '在线' : '离线'
-}
-const getOnlineTagType = (status) => {
-  return Number(status) === 1 ? 'success' : 'info'
-}
-
-// 搜索/筛选变化
-const handleSearch = () => {
-  currentPage.value = 1
-  loadDevices()
-  loadStats()
-}
-
-const resetFilters = () => {
-  searchKeyword.value = ''
-  typeFilter.value = ''
-  currentPage.value = 1
-  loadDevices()
-  loadStats()
-}
-
-const tableRowClassName = ({ row }) => {
-  if (Number(row.onlineStatus) !== 1) return 'inactive-row'
-  return ''
-}
-
-// 详情
-const drawerVisible = ref(false)
-const detailLoading = ref(false)
-const selectedDevice = ref(null)
-
-// 映射详情接口返回数据
-const mapDeviceDetail = (d) => ({
-  equipmentId: d.equipmentId,
-  equipmentTypeId: d.equipmentTypeId,
-  deviceCode: d.equipmentCode,
-  deviceName: d.equipmentName,
-  typeName: d.equipmentTypeName,
-  location: d.equipmentLocation,
-  onlineStatus: d.equipmentStatus?.onlineStatus,
-  lastMaintainDate: d.equipmentStatus?.lastMaintainDate,
-  batteryLevel: d.manholeData?.batteryLevel,
-  lastCommunication: d.manholeData?.uploadTime || d.manholeData?.createTime,
-  metrics: d.manholeData ? {
-    tilt: d.manholeData.tiltAngle,
-    waterIntrusion: Number(d.manholeData.waterInfiltrationAlarmStatus) === 1,
-    temperature: d.manholeData.temperatureValue,
-    signal: d.manholeData.signalStrength
-  } : null,
-  warningThresholds: d.warningThresholdList || []
-})
-
-const showDetail = async (device) => {
-  drawerVisible.value = true
-  detailLoading.value = true
-  try {
-    const res = await getManholeDeviceById(device.equipmentId)
-    selectedDevice.value = res.data ? mapDeviceDetail(res.data) : null
-  } catch (e) {
-    selectedDevice.value = null
-    ElMessage.error('设备详情加载失败')
-  } finally {
-    detailLoading.value = false
-  }
-}
-const handleRowClick = (row) => {
-  showDetail(row)
-}
-const closeDrawer = () => {
-  drawerVisible.value = false
-}
-
-// ============ 关联设备管理 ============
-const relDialogVisible = ref(false)
-const relDevice = ref(null)
-const relList = ref([])
-const relLoading = ref(false)
-const relSearchKeyword = ref('')
-const searchResults = ref([])
-const relSearching = ref(false)
-const searchSelection = ref([])
-const relSaving = ref(false)
-
-const openRelDialog = async (device) => {
-  relDevice.value = device
-  relDialogVisible.value = true
-  relSearchKeyword.value = ''
-  searchResults.value = []
-  searchSelection.value = []
-  await loadRelList(device.equipmentId)
-  await searchRelDevices()
-}
-
-const loadRelList = async (equipmentId) => {
-  relLoading.value = true
-  try {
-    const res = await getManholeDeviceRels(equipmentId)
-    relList.value = res.data || []
-  } catch (e) {
-    relList.value = []
-    ElMessage.error('关联设备列表加载失败')
-  } finally {
-    relLoading.value = false
-  }
-}
-
-const searchRelDevices = async () => {
-  relSearching.value = true
-  try {
-    const keyword = relSearchKeyword.value.trim()
-    // 排除已关联设备ID + 自身设备ID
-    const excludeIds = [
-      ...relList.value.map(r => r.equipmentId),
-      relDevice.value?.equipmentId
-    ].filter(Boolean).join(',')
-    const res = await searchManholeRelDevices({
-      keyword: keyword || undefined,
-      excludeIds: excludeIds || undefined
-    })
-    searchResults.value = res.data || []
-  } catch (e) {
-    searchResults.value = []
-    ElMessage.error('设备搜索失败')
-  } finally {
-    relSearching.value = false
-  }
-}
-
-const handleSearchSelectionChange = (selection) => {
-  searchSelection.value = selection || []
-}
-
-const batchAddRel = async () => {
-  if (!searchSelection.value.length) return
-  relSaving.value = true
-  try {
-    for (const item of searchSelection.value) {
-      await addManholeDeviceRel({
-        manholeId: relDevice.value.equipmentId,
-        equipmentId: item.equipmentId
-      })
-    }
-    ElMessage.success(`已关联 ${searchSelection.value.length} 台设备`)
-    searchSelection.value = []
-    relSearchKeyword.value = ''
-    await loadRelList(relDevice.value.equipmentId)
-    await searchRelDevices()
-  } catch (e) {
-    const msg = e?.message || e?.msg || '关联失败'
-    ElMessage.error(msg)
-  } finally {
-    relSaving.value = false
-  }
-}
-
-const removeRel = async (row) => {
-  try {
-    await deleteManholeDeviceRel(row.relId)
-    ElMessage.success('已取消关联')
-    await loadRelList(relDevice.value.equipmentId)
-    await searchRelDevices()
-  } catch (e) {
-    const msg = e?.message || e?.msg || '取消关联失败'
-    ElMessage.error(msg)
-  }
-}
-
-const onRelDialogClose = () => {
-  relList.value = []
-  searchResults.value = []
-  searchSelection.value = []
-  relSearchKeyword.value = ''
-}
-
-// 初始化
-onMounted(() => {
-  loadTypeOptions()
-  loadDevices()
-  loadStats()
-})
-</script>
-
-<style scoped>
-.manhole-cover-management {
-  padding: 20px;
-  background: #f5f7fa;
-  min-height: 100vh;
-}
-.page-header {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  margin-bottom: 20px;
-  flex-wrap: wrap;
-}
-.stats-section {
-  display: flex;
-  gap: 16px;
-}
-.stat-card {
-  width: 160px;
-  border-radius: 10px;
-}
-.stat-content {
-  display: flex;
-  align-items: center;
-  gap: 12px;
-}
-.stat-content .el-icon {
-  font-size: 32px;
-  color: #409eff;
-}
-.stat-label {
-  font-size: 14px;
-  color: #6c757d;
-}
-.stat-number {
-  font-size: 24px;
-  font-weight: bold;
-  color: #2c3e50;
-}
-.filter-bar {
-  margin-bottom: 20px;
-  display: flex;
-  gap: 12px;
-  flex-wrap: wrap;
-  background: white;
-  padding: 12px 16px;
-  border-radius: 12px;
-  box-shadow: 0 2px 8px rgba(0,0,0,0.05);
-}
-.device-list-container {
-  background: white;
-  border-radius: 12px;
-  box-shadow: 0 2px 12px rgba(0,0,0,0.08);
-  overflow: hidden;
-}
-.pagination-wrapper {
-  display: flex;
-  justify-content: flex-end;
-  padding: 15px 20px;
-  background: white;
-  border-top: 1px solid #e4e7ed;
-}
-.monitor-metrics {
-  background: #f8f9fc;
-  border-radius: 8px;
-  padding: 10px;
-}
-:deep(.inactive-row) {
-  --el-table-tr-bg-color: #f5f5f5;
-  color: #a8a8a8;
-}
-.rel-container {
-  display: flex;
-  flex-direction: column;
-  gap: 20px;
-}
-.rel-section {
-  border: 1px solid #ebeef5;
-  border-radius: 8px;
-  padding: 12px;
-}
-.rel-section-title {
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  margin-bottom: 10px;
-  font-size: 14px;
-  font-weight: 600;
-  color: #2c3e50;
-}
-.rel-search-footer {
-  margin-top: 10px;
-  text-align: right;
-}
-</style>

+ 0 - 876
src/views/subSystem/pipeNetwork/pdeviceInform/PequipmentLed.vue

@@ -1,876 +0,0 @@
-<!-- 物联感知设备管理 -->
-<template>
-  <div class="app-container equipment-page">
-    <div class="summary-grid">
-      <el-card shadow="hover" class="summary-card primary">
-        <div class="summary-label">设备总数</div>
-        <div class="summary-value">{{ summary.total }}</div>
-        <div class="summary-desc">平台已接入物联感知设备</div>
-      </el-card>
-      <el-card shadow="hover" class="summary-card success">
-        <div class="summary-label">在用设备</div>
-        <div class="summary-value">{{ summary.active }}</div>
-        <div class="summary-desc">当前状态为在用的设备</div>
-      </el-card>
-      <el-card shadow="hover" class="summary-card warning">
-        <div class="summary-label">维修/待处理</div>
-        <div class="summary-value">{{ summary.warning }}</div>
-        <div class="summary-desc">维修、待入库、闲置等状态</div>
-      </el-card>
-      <el-card shadow="hover" class="summary-card info">
-        <div class="summary-label">设备类型</div>
-        <div class="summary-value">{{ summary.typeCount }}</div>
-        <div class="summary-desc">当前已维护的设备分类数</div>
-      </el-card>
-    </div>
-
-    <el-card shadow="never" class="query-card">
-      <el-form ref="queryRef" :model="queryParams" :inline="true" v-show="showSearch">
-        <el-form-item label="设备编码" prop="equipmentCode">
-          <el-input
-            v-model="queryParams.equipmentCode"
-            placeholder="请输入设备编码"
-            clearable
-            style="width: 220px"
-            @keyup.enter="handleQuery"
-          />
-        </el-form-item>
-        <el-form-item label="设备名称" prop="equipmentName">
-          <el-input
-            v-model="queryParams.equipmentName"
-            placeholder="请输入设备名称"
-            clearable
-            style="width: 220px"
-            @keyup.enter="handleQuery"
-          />
-        </el-form-item>
-        <el-form-item label="设备型号" prop="equipmentModel">
-          <el-input
-            v-model="queryParams.equipmentModel"
-            placeholder="请输入设备型号"
-            clearable
-            style="width: 220px"
-            @keyup.enter="handleQuery"
-          />
-        </el-form-item>
-        <el-form-item label="制造商" prop="manufacturer">
-          <el-input
-            v-model="queryParams.manufacturer"
-            placeholder="请输入制造商"
-            clearable
-            style="width: 220px"
-            @keyup.enter="handleQuery"
-          />
-        </el-form-item>
-        <el-form-item>
-          <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
-          <el-button icon="Refresh" @click="resetQuery">重置</el-button>
-        </el-form-item>
-      </el-form>
-    </el-card>
-
-    <el-row :gutter="16" class="main-row">
-      <el-col :xs="24" :lg="16">
-        <el-card shadow="never" class="table-card">
-          <template #header>
-            <div class="card-header-row">
-              <div>
-                <div class="card-title">设备台账</div>
-                <div class="card-subtitle">对平台的传感器类型监测设备的基本信息进行管理</div>
-              </div>
-              <div class="card-actions">
-                <el-button type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
-                <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">删除</el-button>
-                <right-toolbar v-model:showSearch="showSearch" @queryTable="getList" />
-              </div>
-            </div>
-          </template>
-
-          <el-table
-            v-loading="loading"
-            :data="equipmentList"
-            highlight-current-row
-            @selection-change="handleSelectionChange"
-            @current-change="handleCurrentChange"
-            @row-click="handleRowClick"
-          >
-            <el-table-column type="selection" width="55" align="center" />
-            <el-table-column label="设备编码" align="center" prop="equipmentCode" min-width="140" />
-            <el-table-column label="设备名称" align="center" prop="equipmentName" min-width="160" show-overflow-tooltip />
-            <el-table-column label="设备型号" align="center" prop="equipmentModel" min-width="120" />
-            <el-table-column label="设备类型" align="center" min-width="140">
-              <template #default="scope">
-                {{ typeNameMap[scope.row.equipmentTypeId] || scope.row.equipmentTypeId || "-" }}
-              </template>
-            </el-table-column>
-            <el-table-column label="资产状态" align="center" width="130">
-              <template #default="scope">
-                <div class="status-cell">
-                  <el-tag :type="getStatusTagType(getRowStatus(scope.row.equipmentId))">
-                    {{ getStatusText(getRowStatus(scope.row.equipmentId)) }}
-                  </el-tag>
-                  <el-tooltip content="资产状态维护" placement="top">
-                    <el-button
-                      link
-                      type="warning"
-                      icon="Edit"
-                      aria-label="资产状态维护"
-                      class="status-edit-button"
-                      @click.stop="openStatusDialog(scope.row)"
-                    />
-                  </el-tooltip>
-                </div>
-              </template>
-            </el-table-column>
-            <el-table-column label="制造商" align="center" prop="manufacturer" min-width="140" show-overflow-tooltip />
-            <el-table-column label="操作" align="center" width="160" class-name="small-padding fixed-width">
-              <template #default="scope">
-                <el-button link type="primary" icon="Edit" @click.stop="handleUpdate(scope.row)">修改</el-button>
-                <el-button link type="success" icon="Aim" @click.stop="openTestDialog(scope.row)">测试验证</el-button>
-              </template>
-            </el-table-column>
-          </el-table>
-
-          <pagination
-            v-show="total > 0"
-            :total="total"
-            v-model:page="queryParams.pageNum"
-            v-model:limit="queryParams.pageSize"
-            @pagination="getList"
-          />
-        </el-card>
-      </el-col>
-
-      <el-col :xs="24" :lg="8">
-        <el-card shadow="never" class="detail-card">
-          <template #header>
-            <div class="card-header-row">
-              <div>
-                <div class="card-title">设备详情</div>
-                <div class="card-subtitle">选中设备后查看基本信息、关联点位与测试记录</div>
-              </div>
-            </div>
-          </template>
-
-          <el-empty v-if="!selectedDetail.equipmentInfo" description="请从左侧选择一台设备" />
-
-          <template v-else>
-            <div class="detail-hero">
-              <div class="hero-title">{{ selectedDetail.equipmentInfo.equipmentName }}</div>
-              <div class="hero-meta">
-                <span>{{ selectedDetail.equipmentInfo.equipmentCode }}</span>
-                <span>{{ selectedDetail.typeInfo?.typeName || "-" }}</span>
-                <span>{{ getStatusText(selectedDetail.statusInfo?.currentStatus) }}</span>
-              </div>
-            </div>
-
-            <el-descriptions :column="1" border size="small">
-              <el-descriptions-item label="设备型号">{{ selectedDetail.equipmentInfo.equipmentModel || "-" }}</el-descriptions-item>
-              <el-descriptions-item label="制造商">{{ selectedDetail.equipmentInfo.manufacturer || "-" }}</el-descriptions-item>
-              <el-descriptions-item label="设备位置">{{ selectedDetail.equipmentInfo.equipmentLocation || "-" }}</el-descriptions-item>
-              <el-descriptions-item label="规格参数">{{ selectedDetail.equipmentInfo.equipmentSpec || "-" }}</el-descriptions-item>
-              <el-descriptions-item label="维护人员">
-                {{ selectedDetail.equipmentInfo.maintainer || "-" }}
-              </el-descriptions-item>
-              <el-descriptions-item label="联系电话">
-                {{ selectedDetail.equipmentInfo.maintainerPhone || "-" }}
-              </el-descriptions-item>
-            </el-descriptions>
-
-            <div class="section-block">
-              <div class="section-title">关联点位</div>
-              <el-table :data="selectedDetail.pointRelList || []" size="small" max-height="180">
-                <el-table-column label="监测点编码" prop="pointCode" min-width="160" show-overflow-tooltip />
-                <el-table-column label="监测点名称" prop="pointName" min-width="160" show-overflow-tooltip />
-              </el-table>
-            </div>
-
-            <div class="section-block">
-              <div class="section-title">测试记录</div>
-              <el-table :data="selectedDetail.testList || []" size="small" max-height="180">
-                <el-table-column label="时间" prop="testTime" min-width="150" />
-                <el-table-column label="类型" prop="testType" min-width="100" />
-                <el-table-column label="结果" prop="testResult" width="80" />
-              </el-table>
-            </div>
-          </template>
-        </el-card>
-      </el-col>
-    </el-row>
-
-    <el-dialog :title="title" v-model="open" width="860px" append-to-body>
-      <el-form ref="equipmentRef" :model="form" :rules="rules" label-width="110px">
-        <el-row :gutter="16">
-          <el-col :span="12">
-            <el-form-item label="设备编码" prop="equipmentCode">
-              <el-input v-model="form.equipmentCode" placeholder="请输入设备编码" />
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="设备名称" prop="equipmentName">
-              <el-input v-model="form.equipmentName" placeholder="请输入设备名称" />
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="设备型号" prop="equipmentModel">
-              <el-input v-model="form.equipmentModel" placeholder="请输入设备型号" />
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="设备类型" prop="equipmentTypeId">
-              <el-tree-select
-                v-model="form.equipmentTypeId"
-                :data="equipmentTypeTree"
-                node-key="id"
-                :props="{ label: 'typeName', children: 'children', disabled: 'disabled' }"
-                default-expand-all
-                :render-after-expand="false"
-                placeholder="请选择设备类型"
-                style="width: 100%"
-              />
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="制造商" prop="manufacturer">
-              <el-input v-model="form.manufacturer" placeholder="请输入制造商" />
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="生产日期" prop="productionDate">
-              <el-date-picker
-                v-model="form.productionDate"
-                type="date"
-                value-format="YYYY-MM-DD"
-                placeholder="请选择生产日期"
-                style="width: 100%"
-              />
-            </el-form-item>
-          </el-col>
-          <el-col :span="24">
-            <el-form-item label="规格参数" prop="equipmentSpec">
-              <el-input v-model="form.equipmentSpec" placeholder="请输入设备规格参数" />
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="资产价值(元)" prop="assetValue">
-              <el-input-number v-model="form.assetValue" :precision="2" :step="100" style="width: 100%" />
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="使用年限(年)" prop="useLife">
-              <el-input-number v-model="form.useLife" :precision="0" :step="1" style="width: 100%" />
-            </el-form-item>
-          </el-col>
-          <el-col :span="24">
-            <el-form-item label="设备位置" prop="equipmentLocation">
-              <el-input v-model="form.equipmentLocation" placeholder="请输入设备位置" />
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="经度" prop="longitude">
-              <el-input-number v-model="form.longitude" :precision="6" :step="0.000001" style="width: 100%" />
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="纬度" prop="latitude">
-              <el-input-number v-model="form.latitude" :precision="6" :step="0.000001" style="width: 100%" />
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="安装时间" prop="installTime">
-              <el-date-picker
-                v-model="form.installTime"
-                type="datetime"
-                value-format="YYYY-MM-DD HH:mm:ss"
-                placeholder="请选择安装时间"
-                style="width: 100%"
-              />
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="维护人员" prop="maintainer">
-              <el-input v-model="form.maintainer" placeholder="请输入维护人员" />
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="联系电话" prop="maintainerPhone">
-              <el-input v-model="form.maintainerPhone" placeholder="请输入联系电话" />
-            </el-form-item>
-          </el-col>
-          <el-col :span="24">
-            <el-form-item label="备注" prop="remark">
-              <el-input v-model="form.remark" type="textarea" :rows="3" placeholder="请输入备注" />
-            </el-form-item>
-          </el-col>
-        </el-row>
-      </el-form>
-      <template #footer>
-        <div class="dialog-footer">
-          <el-button type="primary" @click="submitForm">确 定</el-button>
-          <el-button @click="cancel">取 消</el-button>
-        </div>
-      </template>
-    </el-dialog>
-
-    <el-dialog title="设备状态维护" v-model="statusOpen" width="420px" append-to-body>
-      <el-form label-width="90px">
-        <el-form-item label="设备名称">
-          <span>{{ currentRow.equipmentName || "-" }}</span>
-        </el-form-item>
-        <el-form-item label="资产状态">
-          <el-select v-model="statusForm.currentStatus" style="width: 100%">
-            <el-option
-              v-for="item in equipmentStatusOptions"
-              :key="item.value"
-              :label="item.label"
-              :value="item.value"
-            />
-          </el-select>
-        </el-form-item>
-      </el-form>
-      <template #footer>
-        <div class="dialog-footer">
-          <el-button type="primary" @click="submitStatus">确 定</el-button>
-          <el-button @click="statusOpen = false">取 消</el-button>
-        </div>
-      </template>
-    </el-dialog>
-
-    <el-dialog title="设备测试验证" v-model="testOpen" width="520px" append-to-body>
-      <el-form ref="testRef" :model="testForm" label-width="90px">
-        <el-form-item label="设备名称">
-          <span>{{ currentRow.equipmentName || "-" }}</span>
-        </el-form-item>
-        <el-form-item label="测试类型">
-          <el-input v-model="testForm.testType" placeholder="例如:通信测试" />
-        </el-form-item>
-        <el-form-item label="测试人员">
-          <el-input v-model="testForm.testUser" placeholder="请输入测试人员" />
-        </el-form-item>
-        <el-form-item label="测试详情">
-          <el-input v-model="testForm.testDetail" type="textarea" :rows="4" placeholder="请输入测试详情" />
-        </el-form-item>
-      </el-form>
-      <template #footer>
-        <div class="dialog-footer">
-          <el-button type="primary" @click="submitTest">确 定</el-button>
-          <el-button @click="testOpen = false">取 消</el-button>
-        </div>
-      </template>
-    </el-dialog>
-  </div>
-</template>
-
-<script setup>
-import { computed, getCurrentInstance, onMounted, reactive, ref, toRefs } from "vue";
-import {
-  addEquipment,
-  delEquipment,
-  getEquipment,
-  getEquipmentDetail,
-  listEquipment,
-  listEquipmentStatusAll,
-  listEquipmentTypeAll,
-  testEquipment,
-  updateEquipment,
-  updateEquipmentStatus
-} from "@/api/pipeNetwork/pdeviceInform";
-
-const { proxy } = getCurrentInstance();
-
-const equipmentList = ref([]);
-const equipmentTypeOptions = ref([]);
-const equipmentStatusList = ref([]);
-const loading = ref(false);
-const open = ref(false);
-const statusOpen = ref(false);
-const testOpen = ref(false);
-const showSearch = ref(true);
-const ids = ref([]);
-const multiple = ref(true);
-const total = ref(0);
-const title = ref("");
-const currentRow = ref({});
-const selectedDetail = ref({});
-
-const equipmentStatusOptions = [
-  { label: "在用", value: 1 },
-  { label: "闲置", value: 2 },
-  { label: "维修", value: 3 },
-  { label: "报废", value: 4 },
-  { label: "待入库", value: 5 }
-];
-
-const summary = reactive({
-  total: 0,
-  active: 0,
-  warning: 0,
-  typeCount: 0
-});
-
-const typeNameMap = computed(() => {
-  return equipmentTypeOptions.value.reduce((acc, item) => {
-    acc[item.id] = item.typeName;
-    return acc;
-  }, {});
-});
-
-// parentId 为 0(或空)表示父类
-function isTopLevelType(item) {
-  const parentId = item?.parentId;
-  return parentId === 0 || parentId === "0" || parentId === null || parentId === undefined || parentId === "";
-}
-
-const equipmentTypeTree = computed(() => {
-  const list = equipmentTypeOptions.value || [];
-  const entries = [];
-  // 设备类别主键已统一为整数 id,parentId 指向父记录的 id
-  const nodeIndex = {};
-
-  list.forEach((item) => {
-    const node = {
-      id: item.id,
-      typeName: item.typeName,
-      disabled: isTopLevelType(item),
-      children: []
-    };
-    entries.push({ item, node });
-    if (item.id !== undefined && item.id !== null) {
-      nodeIndex[String(item.id)] = node;
-    }
-  });
-
-  const roots = [];
-  // 父节点不在返回列表中时兜底归类,避免子类型在下拉中丢失
-  const fallbackNode = { id: "__other__", typeName: "未分类", disabled: true, children: [] };
-
-  entries.forEach(({ item, node }) => {
-    if (isTopLevelType(item)) {
-      roots.push(node);
-      return;
-    }
-    const parent = nodeIndex[String(item.parentId)];
-    if (parent && parent !== node) {
-      parent.children.push(node);
-    } else {
-      fallbackNode.children.push(node);
-    }
-  });
-
-  // 带子级的节点(即父类)统一禁选,叶子节点可选;空 children 去掉渲染为叶子
-  const prune = (node) => {
-    if (node.children && node.children.length) {
-      node.disabled = true;
-      node.children.forEach(prune);
-    } else {
-      delete node.children;
-    }
-    return node;
-  };
-
-  const result = roots.map(prune);
-  if (fallbackNode.children.length) {
-    result.push(prune(fallbackNode));
-  }
-  return result;
-});
-
-const statusMap = computed(() => {
-  return equipmentStatusList.value.reduce((acc, item) => {
-    acc[item.equipmentId] = item;
-    return acc;
-  }, {});
-});
-
-const data = reactive({
-  queryParams: {
-    pageNum: 1,
-    pageSize: 10,
-    equipmentCode: undefined,
-    equipmentName: undefined,
-    equipmentModel: undefined,
-    manufacturer: undefined
-  },
-  form: {},
-  statusForm: {
-    currentStatus: 1
-  },
-  testForm: {
-    testType: undefined,
-    testDetail: undefined,
-    testUser: undefined
-  },
-  rules: {
-    equipmentCode: [{ required: true, message: "设备编码不能为空", trigger: "blur" }],
-    equipmentName: [{ required: true, message: "设备名称不能为空", trigger: "blur" }],
-    equipmentTypeId: [{ required: true, message: "设备类型不能为空", trigger: "change" }]
-  }
-});
-
-const { queryParams, form, statusForm, testForm, rules } = toRefs(data);
-
-function createEmptyForm() {
-  return {
-    equipmentId: undefined,
-    equipmentCode: undefined,
-    equipmentName: undefined,
-    equipmentModel: undefined,
-    equipmentSpec: undefined,
-    manufacturer: undefined,
-    productionDate: undefined,
-    equipmentTypeId: undefined,
-    assetValue: undefined,
-    useLife: undefined,
-    longitude: undefined,
-    latitude: undefined,
-    equipmentLocation: undefined,
-    installTime: undefined,
-    maintainer: undefined,
-    maintainerPhone: undefined,
-    remark: undefined
-  };
-}
-
-function getRowStatus(equipmentId) {
-  return statusMap.value[equipmentId]?.currentStatus;
-}
-
-function getStatusText(status) {
-  const found = equipmentStatusOptions.find((item) => item.value === status);
-  return found?.label || "未维护";
-}
-
-function getStatusTagType(status) {
-  const map = {
-    1: "success",
-    2: "info",
-    3: "warning",
-    4: "danger",
-    5: ""
-  };
-  return map[status] || "";
-}
-
-function buildSummary() {
-  summary.total = equipmentStatusList.value.length;
-  summary.active = equipmentStatusList.value.filter((item) => item.currentStatus === 1).length;
-  summary.warning = equipmentStatusList.value.filter((item) => item.currentStatus !== 1).length;
-  summary.typeCount = new Set(equipmentTypeOptions.value.map((item) => item.typeName).filter(Boolean)).size;
-}
-
-function getList() {
-  loading.value = true;
-  return listEquipment(queryParams.value)
-    .then((response) => {
-      equipmentList.value = response.records || [];
-      total.value = response.total || 0;
-      if (!selectedDetail.value.equipmentInfo && equipmentList.value.length) {
-        return handleView(equipmentList.value[0]);
-      }
-      return undefined;
-    })
-    .finally(() => {
-      loading.value = false;
-    });
-}
-
-function loadAssistData() {
-  return Promise.all([listEquipmentTypeAll(), listEquipmentStatusAll()]).then(([typeRes, statusRes]) => {
-    equipmentTypeOptions.value = typeRes.data || [];
-    equipmentStatusList.value = statusRes.data || [];
-    buildSummary();
-  });
-}
-
-function reset() {
-  form.value = createEmptyForm();
-  proxy.resetForm("equipmentRef");
-}
-
-function handleQuery() {
-  queryParams.value.pageNum = 1;
-  getList();
-}
-
-function resetQuery() {
-  proxy.resetForm("queryRef");
-  handleQuery();
-}
-
-function handleSelectionChange(selection) {
-  ids.value = selection.map((item) => item.equipmentId);
-  multiple.value = !selection.length;
-}
-
-function handleCurrentChange(currentRow) {
-  if (currentRow?.equipmentId) {
-    handleView(currentRow);
-  }
-}
-
-function handleRowClick(row) {
-  handleView(row);
-}
-
-function handleAdd() {
-  reset();
-  open.value = true;
-  title.value = "新增物联感知设备";
-}
-
-function handleUpdate(row) {
-  reset();
-  const equipmentId = row?.equipmentId || ids.value[0];
-  if (!equipmentId) {
-    return;
-  }
-  getEquipment(equipmentId).then((response) => {
-    form.value = { ...createEmptyForm(), ...(response.data || {}) };
-    // equipment_type_id 在库中是字符串,而类型树的 node-key 是整数主键,
-    // 统一转成数字才能让 tree-select 命中节点显示类别名称
-    form.value.equipmentTypeId =
-      form.value.equipmentTypeId !== undefined &&
-      form.value.equipmentTypeId !== null &&
-      form.value.equipmentTypeId !== ""
-        ? Number(form.value.equipmentTypeId)
-        : undefined;
-    open.value = true;
-    title.value = "修改物联感知设备";
-  });
-}
-
-function handleView(row) {
-  return getEquipmentDetail(row.equipmentId).then((response) => {
-    selectedDetail.value = response.data || {};
-  });
-}
-
-function cancel() {
-  open.value = false;
-  reset();
-}
-
-function submitForm() {
-  proxy.$refs.equipmentRef.validate((valid) => {
-    if (!valid) {
-      return;
-    }
-    const request = form.value.equipmentId ? updateEquipment(form.value) : addEquipment(form.value);
-    request.then(() => {
-      proxy.$modal.msgSuccess(form.value.equipmentId ? "修改成功" : "新增成功");
-      open.value = false;
-      Promise.all([getList(), loadAssistData()]).then(() => {
-        if (form.value.equipmentId) {
-          handleView({ equipmentId: form.value.equipmentId });
-        }
-      });
-    });
-  });
-}
-
-function handleDelete() {
-  const deleteIds = ids.value;
-  if (!deleteIds.length) {
-    return;
-  }
-  proxy.$modal
-    .confirm(`是否确认删除选中的 ${deleteIds.length} 台设备?`)
-    .then(() => Promise.all(deleteIds.map((id) => delEquipment(id))))
-    .then(() => {
-      proxy.$modal.msgSuccess("删除成功");
-      if (deleteIds.includes(selectedDetail.value?.equipmentInfo?.equipmentId)) {
-        selectedDetail.value = {};
-      }
-      return Promise.all([getList(), loadAssistData()]);
-    });
-}
-
-function openStatusDialog(row) {
-  currentRow.value = row;
-  statusForm.value.currentStatus = getRowStatus(row.equipmentId) || 1;
-  statusOpen.value = true;
-}
-
-function submitStatus() {
-  updateEquipmentStatus(currentRow.value.equipmentId, statusForm.value.currentStatus).then(() => {
-    proxy.$modal.msgSuccess("状态维护成功");
-    statusOpen.value = false;
-    Promise.all([loadAssistData(), handleView(currentRow.value)]);
-  });
-}
-
-function openTestDialog(row) {
-  currentRow.value = row;
-  testForm.value = {
-    testType: "通信测试",
-    testDetail: undefined,
-    testUser: undefined
-  };
-  testOpen.value = true;
-}
-
-function submitTest() {
-  testEquipment(currentRow.value.equipmentId, testForm.value).then(() => {
-    proxy.$modal.msgSuccess("测试记录已提交");
-    testOpen.value = false;
-    handleView(currentRow.value);
-  });
-}
-
-onMounted(() => {
-  reset();
-  Promise.all([getList(), loadAssistData()]);
-});
-</script>
-
-<style scoped>
-.equipment-page {
-  background: #f5f8fc;
-}
-
-.summary-grid {
-  display: grid;
-  grid-template-columns: repeat(4, minmax(0, 1fr));
-  gap: 16px;
-  margin-bottom: 16px;
-}
-
-.summary-card {
-  border: none;
-  color: #fff;
-}
-
-.summary-card :deep(.el-card__body) {
-  padding: 20px;
-}
-
-.summary-card.primary {
-  background: linear-gradient(135deg, #2563eb, #5b93ff);
-}
-
-.summary-card.success {
-  background: linear-gradient(135deg, #059669, #34d399);
-}
-
-.summary-card.warning {
-  background: linear-gradient(135deg, #d97706, #f7b955);
-}
-
-.summary-card.info {
-  background: linear-gradient(135deg, #7c3aed, #9d72ff);
-}
-
-.summary-label {
-  font-size: 14px;
-  opacity: 0.9;
-}
-
-.summary-value {
-  margin: 8px 0 10px;
-  font-size: 30px;
-  font-weight: 700;
-  line-height: 1;
-}
-
-.summary-desc {
-  font-size: 12px;
-  opacity: 0.9;
-}
-
-.query-card,
-.table-card,
-.detail-card {
-  border-radius: 16px;
-}
-
-.query-card {
-  margin-bottom: 16px;
-}
-
-.card-header-row {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  gap: 12px;
-}
-
-.card-title {
-  font-size: 16px;
-  font-weight: 600;
-  color: #1f2937;
-}
-
-.card-subtitle {
-  margin-top: 4px;
-  font-size: 12px;
-  color: #6b7280;
-}
-
-.card-actions {
-  display: flex;
-  align-items: center;
-  gap: 8px;
-  flex-wrap: wrap;
-}
-
-.status-cell {
-  display: inline-flex;
-  align-items: center;
-  gap: 4px;
-}
-
-.status-edit-button {
-  padding: 2px;
-}
-
-.detail-hero {
-  margin-bottom: 16px;
-  padding: 16px;
-  border-radius: 14px;
-  background: linear-gradient(135deg, #eff6ff, #f8fbff);
-  border: 1px solid #dbeafe;
-}
-
-.hero-title {
-  font-size: 18px;
-  font-weight: 700;
-  color: #1d4ed8;
-}
-
-.hero-meta {
-  display: flex;
-  gap: 10px;
-  flex-wrap: wrap;
-  margin-top: 8px;
-  font-size: 12px;
-  color: #4b5563;
-}
-
-.section-block {
-  margin-top: 16px;
-}
-
-.section-title {
-  margin-bottom: 10px;
-  font-size: 15px;
-  font-weight: 600;
-  color: #1f2937;
-}
-
-@media (max-width: 1200px) {
-  .summary-grid {
-    grid-template-columns: repeat(2, minmax(0, 1fr));
-  }
-}
-
-@media (max-width: 768px) {
-  .summary-grid {
-    grid-template-columns: 1fr;
-  }
-
-  .card-header-row {
-    flex-direction: column;
-    align-items: flex-start;
-  }
-}
-</style>

+ 1094 - 0
src/views/subSystem/pipeNetwork/pdeviceInform/index.vue

@@ -0,0 +1,1094 @@
+<!-- 物联感知设备管理 -->
+<template>
+  <div class="app-container equipment-page">
+    <!-- 页头 -->
+    <div class="page-head">
+      <div class="page-head-titles">
+        <div class="page-title">设备管理</div>
+      </div>
+      <el-button type="primary" icon="Plus" class="btn-primary" @click="handleAdd">新增设备</el-button>
+    </div>
+
+    <!-- KPI -->
+    <div class="kpi-row">
+      <el-card shadow="never" class="kpi">
+        <div class="kpi-icon total"><el-icon><monitor /></el-icon></div>
+        <div class="kpi-data">
+          <div class="kpi-label">设备总数(台)</div>
+          <div class="kpi-value">{{ statistics.total }}</div>
+        </div>
+      </el-card>
+      <el-card shadow="never" class="kpi">
+        <div class="kpi-icon online"><el-icon><circle-check /></el-icon></div>
+        <div class="kpi-data">
+          <div class="kpi-label">在线设备(台)</div>
+          <div class="kpi-value">{{ statistics.online }}</div>
+        </div>
+      </el-card>
+      <el-card shadow="never" class="kpi">
+        <div class="kpi-icon offline"><el-icon><circle-close /></el-icon></div>
+        <div class="kpi-data">
+          <div class="kpi-label">离线设备(台)</div>
+          <div class="kpi-value">{{ statistics.offline }}</div>
+        </div>
+      </el-card>
+      <el-card shadow="never" class="kpi">
+        <div class="kpi-icon alarm"><el-icon><warning /></el-icon></div>
+        <div class="kpi-data">
+          <div class="kpi-label">报警设备(台)</div>
+          <div class="kpi-value">{{ statistics.alarm }}</div>
+        </div>
+      </el-card>
+    </div>
+
+    <!-- 设备台账 -->
+    <el-card shadow="never" class="ledger">
+      <div class="ledger-head">
+        <div class="ledger-title">设备台账</div>
+        <div class="ledger-sub">一览管网监测设备台账与运行状态</div>
+      </div>
+
+      <el-form ref="queryRef" :model="queryParams" :inline="true" class="filters">
+        <el-form-item label="设备编码" prop="equipmentCode">
+          <el-input
+            v-model="queryParams.equipmentCode"
+            placeholder="请输入"
+            clearable
+            class="input--code"
+            @keyup.enter="handleQuery"
+          />
+        </el-form-item>
+        <el-form-item label="设备名称" prop="equipmentName">
+          <el-input
+            v-model="queryParams.equipmentName"
+            placeholder="请输入"
+            clearable
+            class="input--name"
+            @keyup.enter="handleQuery"
+          />
+        </el-form-item>
+        <el-form-item label="设备类型" prop="equipmentTypeId">
+          <el-tree-select
+            v-model="queryParams.equipmentTypeId"
+            :data="equipmentTypeTree"
+            node-key="id"
+            :props="{ label: 'typeName', children: 'children', disabled: 'disabled' }"
+            check-strictly
+            :render-after-expand="false"
+            clearable
+            placeholder="请选择"
+            class="select--type"
+          />
+        </el-form-item>
+        <el-form-item label="在线状态" prop="onlineStatus">
+          <el-select v-model="queryParams.onlineStatus" clearable placeholder="请选择" class="select--online">
+            <el-option label="在线" :value="1" />
+            <el-option label="离线" :value="0" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="报警" prop="alarmOnly">
+          <el-checkbox v-model="alarmOnly" @change="handleQuery" />
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" class="btn-query" @click="handleQuery">查询</el-button>
+          <el-button class="btn-reset" @click="resetQuery">重置</el-button>
+        </el-form-item>
+      </el-form>
+
+      <div v-loading="loading" class="grid">
+        <el-empty v-if="!equipmentList.length" description="暂无设备数据" />
+        <div
+          v-for="row in equipmentList"
+          :key="row.equipmentId"
+          class="card"
+          :class="{ 'is-selected': selectedId === row.equipmentId, 'is-alarm': getRowAlarm(row.equipmentId) === 1 }"
+          @click="handleView(row)"
+        >
+          <div class="card-top">
+            <div class="card-name">{{ row.equipmentName || "未命名设备" }}</div>
+            <div class="status-group">
+              <span class="badge" :class="getRowOnline(row.equipmentId) === 1 ? 'online' : 'offline'">
+                <span class="badge-dot"></span>
+                <span class="badge-text">{{ getRowOnline(row.equipmentId) === 1 ? "在线" : "离线" }}</span>
+              </span>
+              <span v-if="getRowAlarm(row.equipmentId) === 1" class="badge alarm">
+                <el-icon class="badge-icon"><warning-filled /></el-icon>
+                <span class="badge-text">报警</span>
+              </span>
+            </div>
+          </div>
+
+          <div class="card-code">{{ row.equipmentCode }}</div>
+
+          <div class="card-bottom">
+            <span class="type-tag">
+              {{ (row.equipmentTypeId && typeNameMap[row.equipmentTypeId]) || "未分类" }}
+            </span>
+            <div class="card-actions">
+              <el-button link class="link-detail" @click.stop="handleView(row)">详情</el-button>
+              <el-button link class="link-delete" @click.stop="handleDelete(row)">删除</el-button>
+            </div>
+          </div>
+        </div>
+      </div>
+
+      <pagination
+        v-show="total > 0"
+        :total="total"
+        v-model:page="queryParams.pageNum"
+        v-model:limit="queryParams.pageSize"
+        @pagination="getList"
+      />
+    </el-card>
+
+    <!-- 设备详情弹窗 -->
+    <el-dialog title="设备详情" v-model="detailOpen" width="800px" append-to-body class="detail-dialog">
+      <template v-if="selectedDetail.equipmentInfo">
+        <div class="dlg-summary">
+          <div class="summary-name">{{ selectedDetail.equipmentInfo.equipmentName || "未命名设备" }}</div>
+          <div class="summary-code">{{ selectedDetail.equipmentInfo.equipmentCode }}</div>
+          <span class="summary-tag">
+            {{ selectedDetail.typeInfo?.typeName || (selectedDetail.equipmentInfo.equipmentTypeId && typeNameMap[selectedDetail.equipmentInfo.equipmentTypeId]) || "未分类" }}
+          </span>
+          <div class="status-group">
+            <span class="badge" :class="selectedDetail.statusInfo?.onlineStatus === 1 ? 'online' : 'offline'">
+              <span class="badge-dot"></span>
+              <span class="badge-text">{{ selectedDetail.statusInfo?.onlineStatus === 1 ? "在线" : "离线" }}</span>
+            </span>
+            <span v-if="selectedDetail.statusInfo?.alarmStatus === 1" class="badge alarm">
+              <el-icon class="badge-icon"><warning-filled /></el-icon>
+              <span class="badge-text">报警</span>
+            </span>
+          </div>
+        </div>
+
+        <el-descriptions :column="2" border size="small" class="dlg-grid">
+          <el-descriptions-item label="设备编码">{{ selectedDetail.equipmentInfo.equipmentCode || "-" }}</el-descriptions-item>
+          <el-descriptions-item label="设备名称">{{ selectedDetail.equipmentInfo.equipmentName || "-" }}</el-descriptions-item>
+          <el-descriptions-item label="设备型号">{{ selectedDetail.equipmentInfo.equipmentModel || "-" }}</el-descriptions-item>
+          <el-descriptions-item label="设备类型">{{ selectedDetail.typeInfo?.typeName || "-" }}</el-descriptions-item>
+          <el-descriptions-item label="制造商">{{ selectedDetail.equipmentInfo.manufacturer || "-" }}</el-descriptions-item>
+          <el-descriptions-item label="生产日期">{{ selectedDetail.equipmentInfo.productionDate || "-" }}</el-descriptions-item>
+          <el-descriptions-item label="规格参数" :span="2">{{ selectedDetail.equipmentInfo.equipmentSpec || "-" }}</el-descriptions-item>
+          <el-descriptions-item label="资产价值(元)">{{ selectedDetail.equipmentInfo.assetValue ?? "-" }}</el-descriptions-item>
+          <el-descriptions-item label="使用年限(年)">{{ selectedDetail.equipmentInfo.useLife ?? "-" }}</el-descriptions-item>
+          <el-descriptions-item label="资产状态">{{ currentStatusMeta(selectedDetail.statusInfo?.currentStatus).text }}</el-descriptions-item>
+          <el-descriptions-item label="所属片区">{{ selectedDetail.equipmentInfo.district || "-" }}</el-descriptions-item>
+          <el-descriptions-item label="设备位置" :span="2">{{ selectedDetail.equipmentInfo.equipmentLocation || "-" }}</el-descriptions-item>
+          <el-descriptions-item label="经度">{{ selectedDetail.equipmentInfo.longitude ?? "-" }}</el-descriptions-item>
+          <el-descriptions-item label="纬度">{{ selectedDetail.equipmentInfo.latitude ?? "-" }}</el-descriptions-item>
+          <el-descriptions-item label="安装时间">{{ selectedDetail.equipmentInfo.installTime || "-" }}</el-descriptions-item>
+          <el-descriptions-item label="维护人员">{{ selectedDetail.equipmentInfo.maintainer || "-" }}</el-descriptions-item>
+          <el-descriptions-item label="联系电话">{{ selectedDetail.equipmentInfo.maintainerPhone || "-" }}</el-descriptions-item>
+          <el-descriptions-item label="备注" :span="2">{{ selectedDetail.equipmentInfo.remark || "-" }}</el-descriptions-item>
+        </el-descriptions>
+
+        <div v-if="selectedDetail.equipmentInfo.equipmentImage" class="detail-image">
+          <div class="field-label">设备图片</div>
+          <el-image
+            :src="resolveImageUrl(selectedDetail.equipmentInfo.equipmentImage)"
+            :preview-src-list="[resolveImageUrl(selectedDetail.equipmentInfo.equipmentImage)]"
+            preview-teleported
+            fit="cover"
+            class="field-media"
+          />
+        </div>
+      </template>
+      <el-empty v-else description="未选中设备" />
+      <template #footer>
+        <div class="dlg-footer">
+          <el-button class="btn-ghost" @click="detailOpen = false">关 闭</el-button>
+          <el-button type="primary" icon="Edit" @click="handleUpdate(selectedDetail.equipmentInfo)">编辑设备</el-button>
+        </div>
+      </template>
+    </el-dialog>
+
+    <!-- 新增/修改弹窗 -->
+    <el-dialog :title="title" v-model="open" width="860px" append-to-body>
+      <el-form ref="equipmentRef" :model="form" :rules="rules" label-width="110px">
+        <el-row :gutter="16">
+          <el-col :span="12">
+            <el-form-item label="设备编码" prop="equipmentCode">
+              <el-input v-model="form.equipmentCode" placeholder="请输入设备编码" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="设备名称" prop="equipmentName">
+              <el-input v-model="form.equipmentName" placeholder="请输入设备名称" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="设备型号" prop="equipmentModel">
+              <el-input v-model="form.equipmentModel" placeholder="请输入设备型号" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="设备类型" prop="equipmentTypeId">
+              <el-tree-select
+                v-model="form.equipmentTypeId"
+                :data="equipmentTypeTree"
+                node-key="id"
+                :props="{ label: 'typeName', children: 'children', disabled: 'disabled' }"
+                default-expand-all
+                :render-after-expand="false"
+                placeholder="请选择设备类型"
+                style="width: 100%"
+              />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="制造商" prop="manufacturer">
+              <el-input v-model="form.manufacturer" placeholder="请输入制造商" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="生产日期" prop="productionDate">
+              <el-date-picker
+                v-model="form.productionDate"
+                type="date"
+                value-format="YYYY-MM-DD"
+                placeholder="请选择生产日期"
+                style="width: 100%"
+              />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="资产状态" prop="currentStatus">
+              <el-select v-model="form.currentStatus" placeholder="请选择资产状态" style="width: 100%">
+                <el-option
+                  v-for="item in equipmentStatusOptions"
+                  :key="item.value"
+                  :label="item.label"
+                  :value="item.value"
+                />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="所属片区" prop="district">
+              <el-select v-model="form.district" clearable placeholder="请选择所属片区" style="width: 100%">
+                <el-option v-for="item in districtOptions" :key="item" :label="item" :value="item" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="24">
+            <el-form-item label="规格参数" prop="equipmentSpec">
+              <el-input v-model="form.equipmentSpec" placeholder="请输入设备规格参数" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="资产价值(元)" prop="assetValue">
+              <el-input-number v-model="form.assetValue" :precision="2" :step="100" style="width: 100%" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="使用年限(年)" prop="useLife">
+              <el-input-number v-model="form.useLife" :precision="0" :step="1" style="width: 100%" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="24">
+            <el-form-item label="设备位置" prop="equipmentLocation">
+              <el-input v-model="form.equipmentLocation" placeholder="请输入设备位置" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="经度" prop="longitude">
+              <el-input-number v-model="form.longitude" :precision="6" :step="0.000001" style="width: 100%" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="纬度" prop="latitude">
+              <el-input-number v-model="form.latitude" :precision="6" :step="0.000001" style="width: 100%" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="安装时间" prop="installTime">
+              <el-date-picker
+                v-model="form.installTime"
+                type="datetime"
+                value-format="YYYY-MM-DD HH:mm:ss"
+                placeholder="请选择安装时间"
+                style="width: 100%"
+              />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="维护人员" prop="maintainer">
+              <el-input v-model="form.maintainer" placeholder="请输入维护人员" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="联系电话" prop="maintainerPhone">
+              <el-input v-model="form.maintainerPhone" placeholder="请输入联系电话" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="24">
+            <el-form-item label="备注" prop="remark">
+              <el-input v-model="form.remark" type="textarea" :rows="3" placeholder="请输入备注" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="24">
+            <el-form-item label="设备图片" prop="equipmentImage">
+              <image-upload v-model="form.equipmentImage" :limit="1" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="primary" @click="submitForm">确 定</el-button>
+          <el-button @click="cancel">取 消</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import { computed, getCurrentInstance, onMounted, reactive, ref, toRefs } from "vue";
+import { useRoute } from "vue-router";
+import {
+  addEquipment,
+  delEquipment,
+  getEquipment,
+  getEquipmentDetail,
+  getEquipmentStatusStatistics,
+  listEquipment,
+  listEquipmentStatusAll,
+  listEquipmentTypeAll,
+  updateEquipment,
+  updateEquipmentStatus
+} from "@/api/pipeNetwork/pdeviceInform";
+
+const { proxy } = getCurrentInstance();
+
+// 数据范围:由系统菜单 query 配置 {"rootTypeId":1} 注入,对应 EquipmentTypeEnum 的 ROOT_ 大类
+const route = useRoute();
+const rootTypeId = ref(route.query.rootTypeId ? Number(route.query.rootTypeId) : undefined);
+
+const equipmentList = ref([]);
+const allTypeList = ref([]);
+const equipmentStatusList = ref([]);
+const loading = ref(false);
+const open = ref(false);
+const detailOpen = ref(false);
+const alarmOnly = ref(false);
+const total = ref(0);
+const title = ref("");
+const selectedDetail = ref({});
+const selectedId = ref("");
+
+const statistics = reactive({
+  total: 0,
+  online: 0,
+  offline: 0,
+  alarm: 0
+});
+
+// 资产状态:0-在用,1-维修,2-报废
+const equipmentStatusOptions = [
+  { label: "在用", value: 0 },
+  { label: "维修", value: 1 },
+  { label: "报废", value: 2 }
+];
+
+// 所属片区
+const districtOptions = ["城南", "城北", "太常"];
+
+const typeNameMap = computed(() => {
+  return allTypeList.value.reduce((acc, item) => {
+    acc[item.id] = item.typeName;
+    return acc;
+  }, {});
+});
+
+// 设备状态索引:equipmentId -> EquipmentStatus
+const statusMap = computed(() => {
+  return equipmentStatusList.value.reduce((acc, item) => {
+    acc[item.equipmentId] = item;
+    return acc;
+  }, {});
+});
+
+function getRowStatus(equipmentId) {
+  return statusMap.value[equipmentId]?.currentStatus;
+}
+
+function getRowOnline(equipmentId) {
+  return statusMap.value[equipmentId]?.onlineStatus;
+}
+
+function getRowAlarm(equipmentId) {
+  return statusMap.value[equipmentId]?.alarmStatus;
+}
+
+// parentId 为 0(或空)表示父类
+function isTopLevelType(item) {
+  const parentId = item?.parentId;
+  return parentId === 0 || parentId === "0" || parentId === null || parentId === undefined || parentId === "";
+}
+
+function buildTypeTree(list) {
+  const entries = [];
+  const nodeIndex = {};
+
+  list.forEach((item) => {
+    const node = {
+      id: item.id,
+      typeName: item.typeName,
+      disabled: isTopLevelType(item),
+      children: []
+    };
+    entries.push({ item, node });
+    if (item.id !== undefined && item.id !== null) {
+      nodeIndex[String(item.id)] = node;
+    }
+  });
+
+  const roots = [];
+  const fallbackNode = { id: "__other__", typeName: "未分类", disabled: true, children: [] };
+
+  entries.forEach(({ item, node }) => {
+    if (isTopLevelType(item)) {
+      roots.push(node);
+      return;
+    }
+    const parent = nodeIndex[String(item.parentId)];
+    if (parent && parent !== node) {
+      parent.children.push(node);
+    } else {
+      fallbackNode.children.push(node);
+    }
+  });
+
+  const prune = (node) => {
+    if (node.children && node.children.length) {
+      node.disabled = true;
+      node.children.forEach(prune);
+    } else {
+      delete node.children;
+    }
+    return node;
+  };
+
+  const result = roots.map(prune);
+  if (fallbackNode.children.length) {
+    result.push(prune(fallbackNode));
+  }
+  return result;
+}
+
+// 全量类型构建树后按 rootTypeId 取对应大类分支:
+// 父节点展示真实大类名称(如“供水设备”),而非笼统的“未分类”
+const equipmentTypeTree = computed(() => {
+  const tree = buildTypeTree(allTypeList.value || []);
+  if (!rootTypeId.value) {
+    return tree;
+  }
+  const branch = tree.find((node) => String(node.id) === String(rootTypeId.value));
+  return branch ? [branch] : tree;
+});
+
+function currentStatusMeta(value) {
+  const map = {
+    0: { text: "在用", type: "success" },
+    1: { text: "维修", type: "warning" },
+    2: { text: "报废", type: "danger" }
+  };
+  return map[value] || { text: "未知", type: "info" };
+}
+
+const baseUrl = import.meta.env.VITE_APP_BASE_API;
+
+function resolveImageUrl(url) {
+  if (!url) return "";
+  const value = String(url).trim();
+  if (/^(https?:)?\/\//i.test(value) || value.startsWith("data:")) return value;
+  return baseUrl + (value.startsWith("/") ? value : "/" + value);
+}
+
+const data = reactive({
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+    equipmentCode: undefined,
+    equipmentName: undefined,
+    equipmentTypeId: undefined,
+    onlineStatus: undefined
+  },
+  form: {},
+  rules: {
+    equipmentCode: [{ required: true, message: "设备编码不能为空", trigger: "blur" }],
+    equipmentName: [{ required: true, message: "设备名称不能为空", trigger: "blur" }],
+    equipmentTypeId: [{ required: true, message: "设备类型不能为空", trigger: "change" }]
+  }
+});
+
+const { queryParams, form, rules } = toRefs(data);
+
+function createEmptyForm() {
+  return {
+    equipmentId: undefined,
+    equipmentCode: undefined,
+    equipmentName: undefined,
+    equipmentModel: undefined,
+    equipmentSpec: undefined,
+    manufacturer: undefined,
+    productionDate: undefined,
+    equipmentTypeId: undefined,
+    district: undefined,
+    currentStatus: 0,
+    assetValue: undefined,
+    useLife: undefined,
+    longitude: undefined,
+    latitude: undefined,
+    equipmentLocation: undefined,
+    installTime: undefined,
+    maintainer: undefined,
+    maintainerPhone: undefined,
+    equipmentImage: undefined,
+    remark: undefined
+  };
+}
+
+function getList() {
+  loading.value = true;
+  return listEquipment({
+    ...queryParams.value,
+    rootTypeId: rootTypeId.value,
+    alarmStatus: alarmOnly.value ? 1 : undefined
+  })
+    .then((response) => {
+      equipmentList.value = response.records || [];
+      total.value = response.total || 0;
+    })
+    .finally(() => {
+      loading.value = false;
+    });
+}
+
+function loadAssistData() {
+  return Promise.all([listEquipmentTypeAll(), listEquipmentStatusAll(rootTypeId.value)]).then(([typeRes, statusRes]) => {
+    allTypeList.value = typeRes.data || [];
+    equipmentStatusList.value = statusRes.data || [];
+  });
+}
+
+function loadStatistics() {
+  return getEquipmentStatusStatistics(rootTypeId.value).then((res) => {
+    const data = res.data || {};
+    statistics.total = data.total || 0;
+    statistics.online = data.online || 0;
+    statistics.offline = data.offline || 0;
+    statistics.alarm = data.alarm || 0;
+  });
+}
+
+function reset() {
+  form.value = createEmptyForm();
+  proxy.resetForm("equipmentRef");
+}
+
+function handleQuery() {
+  queryParams.value.pageNum = 1;
+  getList();
+}
+
+function resetQuery() {
+  proxy.resetForm("queryRef");
+  alarmOnly.value = false;
+  handleQuery();
+}
+
+function handleAdd() {
+  reset();
+  open.value = true;
+  title.value = "新增设备";
+}
+
+function handleUpdate(row) {
+  detailOpen.value = false;
+  reset();
+  const equipmentId = row?.equipmentId;
+  if (!equipmentId) {
+    return;
+  }
+  getEquipment(equipmentId).then((response) => {
+    form.value = { ...createEmptyForm(), ...(response.data || {}) };
+    form.value.equipmentTypeId =
+      form.value.equipmentTypeId !== undefined &&
+      form.value.equipmentTypeId !== null &&
+      form.value.equipmentTypeId !== ""
+        ? Number(form.value.equipmentTypeId)
+        : undefined;
+    form.value.currentStatus = getRowStatus(equipmentId) ?? 0;
+    open.value = true;
+    title.value = "修改设备";
+  });
+}
+
+function handleView(row) {
+  if (!row?.equipmentId) {
+    return;
+  }
+  selectedId.value = row.equipmentId;
+  return getEquipmentDetail(row.equipmentId).then((response) => {
+    selectedDetail.value = response.data || {};
+    detailOpen.value = true;
+  });
+}
+
+function handleDelete(row) {
+  if (!row?.equipmentId) {
+    return;
+  }
+  proxy.$modal
+    .confirm(`即将删除 ${row.equipmentName || "该设备"}(${row.equipmentCode}),确认继续?`)
+    .then(() => delEquipment(row.equipmentId))
+    .then(() => {
+      proxy.$modal.msgSuccess("删除成功");
+      if (selectedDetail.value?.equipmentInfo?.equipmentId === row.equipmentId) {
+        selectedDetail.value = {};
+        selectedId.value = "";
+        detailOpen.value = false;
+      }
+      return Promise.all([getList(), loadAssistData(), loadStatistics()]);
+    });
+}
+
+function cancel() {
+  open.value = false;
+  reset();
+}
+
+function submitForm() {
+  proxy.$refs.equipmentRef.validate((valid) => {
+    if (!valid) {
+      return;
+    }
+    const isEdit = !!form.value.equipmentId;
+    const equipmentId = form.value.equipmentId;
+    const currentStatus = form.value.currentStatus;
+    const request = isEdit ? updateEquipment(form.value) : addEquipment(form.value);
+    request.then(() => {
+      proxy.$modal.msgSuccess(isEdit ? "修改成功" : "新增成功");
+      open.value = false;
+      const tasks = [getList(), loadAssistData(), loadStatistics()];
+      if (isEdit && currentStatus !== undefined && currentStatus !== null) {
+        tasks.push(updateEquipmentStatus(equipmentId, currentStatus));
+      }
+      Promise.all(tasks).then(() => {
+        if (isEdit) {
+          handleView({ equipmentId });
+        }
+      });
+    });
+  });
+}
+
+onMounted(() => {
+  reset();
+  Promise.all([getList(), loadAssistData(), loadStatistics()]);
+});
+</script>
+
+<style scoped>
+.equipment-page {
+  background: #f9f9f9;
+}
+
+/* 页头 */
+.page-head {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  margin-bottom: 20px;
+}
+
+.page-title {
+  font-size: 20px;
+  font-weight: 600;
+  color: #0a1628;
+  line-height: 1.4;
+}
+
+.btn-primary :deep(.el-icon) {
+  font-size: 14px;
+}
+
+/* KPI */
+.kpi-row {
+  display: flex;
+  gap: 16px;
+  margin-bottom: 20px;
+}
+
+.kpi {
+  flex: 1 1 0;
+  min-width: 0;
+  border-radius: 12px;
+}
+
+.kpi :deep(.el-card__body) {
+  display: flex;
+  align-items: center;
+  gap: 16px;
+  padding: 20px;
+}
+
+.kpi-icon {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 44px;
+  height: 44px;
+  flex: 0 0 44px;
+  border-radius: 10px;
+  font-size: 22px;
+}
+
+.kpi-icon.total {
+  background: #edf3fc;
+  color: #014db2;
+}
+
+.kpi-icon.online {
+  background: #e7f8f1;
+  color: #10b981;
+}
+
+.kpi-icon.offline {
+  background: #fef4e2;
+  color: #f59e0b;
+}
+
+.kpi-icon.alarm {
+  background: #fdecec;
+  color: #ef4444;
+}
+
+.kpi-label {
+  font-size: 13px;
+  font-weight: 400;
+  color: #6b7280;
+}
+
+.kpi-value {
+  margin-top: 6px;
+  font-size: 28px;
+  font-weight: 700;
+  line-height: 1;
+  color: #0a1628;
+}
+
+/* 台账卡片 */
+.ledger {
+  border-radius: 12px;
+}
+
+.ledger :deep(.el-card__body) {
+  display: flex;
+  flex-direction: column;
+  gap: 14px;
+  padding: 20px;
+}
+
+.ledger-head {
+  display: flex;
+  align-items: baseline;
+  gap: 8px;
+}
+
+.ledger-title {
+  font-size: 16px;
+  font-weight: 600;
+  color: #0a1628;
+}
+
+.ledger-sub {
+  font-size: 12px;
+  font-weight: 400;
+  color: #9ca3af;
+}
+
+/* 筛选栏 */
+.filters {
+  display: flex;
+  align-items: center;
+  flex-wrap: wrap;
+  gap: 4px 16px;
+}
+
+.filters :deep(.el-form-item) {
+  margin-right: 0;
+  margin-bottom: 0;
+}
+
+.filters :deep(.el-form-item__label) {
+  font-size: 12px;
+  font-weight: 400;
+  color: #6b7280;
+  padding-right: 8px;
+}
+
+.input--code,
+.input--name {
+  width: 150px;
+}
+
+.select--type {
+  width: 150px;
+}
+
+.select--online {
+  width: 120px;
+}
+
+.btn-query {
+  height: 34px;
+  padding: 0 16px;
+  border-radius: 8px;
+  font-size: 13px;
+  font-weight: 500;
+}
+
+.btn-reset {
+  height: 34px;
+  padding: 0 16px;
+  border-radius: 8px;
+  font-size: 13px;
+  font-weight: 400;
+}
+
+/* 设备卡片 */
+.grid {
+  display: grid;
+  grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
+  gap: 12px;
+  min-height: 200px;
+}
+
+.card {
+  display: flex;
+  flex-direction: column;
+  gap: 10px;
+  padding: 14px;
+  border-radius: 10px;
+  background: #fff;
+  border: 1px solid #ebebed;
+  cursor: pointer;
+  transition: border-color 0.2s, box-shadow 0.2s, background 0.2s;
+}
+
+.card:hover {
+  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06), 0 1px 2px rgba(0, 0, 0, 0.04);
+}
+
+.card.is-selected {
+  background: #f5f9ff;
+  border: 1.5px solid #014db2;
+}
+
+.card.is-alarm {
+  border: 1.5px solid #fca5a5;
+}
+
+.card-top {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  gap: 8px;
+}
+
+.card-name {
+  min-width: 0;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+  font-size: 14px;
+  font-weight: 500;
+  color: #0a1628;
+}
+
+.status-group {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+  flex-shrink: 0;
+}
+
+.badge {
+  display: flex;
+  align-items: center;
+  gap: 5px;
+}
+
+.badge-dot {
+  width: 6px;
+  height: 6px;
+  border-radius: 3px;
+  flex-shrink: 0;
+}
+
+.badge-text {
+  font-size: 12px;
+  font-weight: 500;
+}
+
+.badge.online .badge-dot {
+  background: #10b981;
+}
+
+.badge.online .badge-text {
+  color: #10b981;
+}
+
+.badge.offline .badge-dot {
+  background: #f59e0b;
+}
+
+.badge.offline .badge-text {
+  color: #f59e0b;
+}
+
+.badge.alarm .badge-text {
+  color: #ef4444;
+}
+
+.badge-icon {
+  font-size: 13px;
+  color: #ef4444;
+}
+
+.card-code {
+  font-size: 12px;
+  font-weight: 400;
+  color: #9ca3af;
+}
+
+.card-bottom {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  gap: 8px;
+}
+
+.type-tag {
+  display: inline-flex;
+  align-items: center;
+  padding: 3px 8px;
+  border-radius: 4px;
+  background: #edf3fc;
+  font-size: 11px;
+  font-weight: 500;
+  color: #014db2;
+}
+
+.card-actions {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+}
+
+.card-actions :deep(.el-button) {
+  height: auto;
+  padding: 0;
+  font-size: 12px;
+  font-weight: 500;
+}
+
+.link-detail {
+  color: #014db2;
+}
+
+.link-delete {
+  color: #ef4444;
+}
+
+/* 详情弹窗 */
+.dlg-summary {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  flex-wrap: wrap;
+  padding: 14px;
+  border-radius: 8px;
+  background: #f5f9ff;
+}
+
+.summary-name {
+  font-size: 15px;
+  font-weight: 500;
+  color: #014db2;
+}
+
+.summary-code {
+  font-size: 12px;
+  font-weight: 400;
+  color: #6b7280;
+}
+
+.summary-tag {
+  display: inline-flex;
+  align-items: center;
+  padding: 3px 8px;
+  border-radius: 4px;
+  background: #fff;
+  font-size: 11px;
+  font-weight: 500;
+  color: #014db2;
+}
+
+.dlg-grid {
+  margin-top: 16px;
+}
+
+.detail-image {
+  margin-top: 16px;
+}
+
+.field-label {
+  margin-bottom: 6px;
+  font-size: 12px;
+  font-weight: 400;
+  color: #6b7280;
+}
+
+.field-media {
+  width: 320px;
+  max-width: 100%;
+  height: 180px;
+  border-radius: 6px;
+  border: 1px solid #e6e6eb;
+}
+
+.dlg-footer,
+.dialog-footer {
+  display: flex;
+  align-items: center;
+  justify-content: flex-end;
+  gap: 10px;
+}
+
+@media (max-width: 1200px) {
+  .kpi-row {
+    flex-wrap: wrap;
+  }
+
+  .kpi {
+    flex: 1 1 45%;
+  }
+}
+
+@media (max-width: 768px) {
+  .kpi {
+    flex: 1 1 100%;
+  }
+
+  .page-head {
+    flex-direction: column;
+    align-items: flex-start;
+    gap: 12px;
+  }
+}
+</style>

+ 0 - 1404
src/views/subSystem/waterSupply/components/WaterDevicePage.vue

@@ -1,1404 +0,0 @@
-<template>
-    <div class="water-page device-page">
-        <div class="page-heading">
-            <div class="page-title">
-                <span class="page-title-icon"><el-icon><component :is="pageIcon"/></el-icon></span>
-                <div><h2>{{ title }}</h2><span class="muted">{{ subtitle }}</span></div>
-            </div>
-            <el-button class="heading-refresh" :icon="Refresh" :loading="loading" @click="loadData">刷新</el-button>
-        </div>
-
-        <div v-if="view === 'abnormal'" class="metric-grid">
-            <div v-for="item in abnormalStats" :key="item.label" class="metric">
-                <div class="metric-icon">
-                    <el-icon>
-                        <component :is="item.icon"/>
-                    </el-icon>
-                </div>
-                <div class="metric-body">
-                    <span>{{ item.label }}</span>
-                    <div class="metric-value"><strong>{{ item.value }}</strong><small>{{ item.unit }}</small></div>
-                </div>
-            </div>
-        </div>
-
-        <div v-if="view === 'ledger'" class="metric-grid">
-            <div v-for="item in ledgerStatsCards" :key="item.label" class="metric">
-                <div class="metric-icon">
-                    <el-icon>
-                        <component :is="item.icon"/>
-                    </el-icon>
-                </div>
-                <div class="metric-body">
-                    <span>{{ item.label }}</span>
-                    <div class="metric-value"><strong>{{ item.value }}</strong><small>{{ item.unit }}</small></div>
-                </div>
-            </div>
-        </div>
-
-        <el-card shadow="never" class="filter-card">
-            <el-form :inline="true" :model="query">
-                <el-form-item v-if="view !== 'gis'" label="设备名称">
-                    <el-input v-model="query.equipmentName" clearable/>
-                </el-form-item>
-                <el-form-item v-if="view !== 'gis'" label="设备编码">
-                    <el-input v-model="query.equipmentCode" clearable/>
-                </el-form-item>
-                <el-form-item v-if="view === 'ledger'" label="报警状态">
-                    <el-select v-model="query.alarmStatus" clearable class="filter-select" style="width:180px">
-                        <el-option label="正常" :value="0"/>
-                        <el-option label="报警" :value="1"/>
-                    </el-select>
-                </el-form-item>
-                <el-form-item v-if="view === 'ledger' || view === 'dispatch' || view === 'abnormal'" label="报警等级">
-                    <el-select v-model="query.alertLevel" clearable class="filter-select" style="width:180px">
-                        <el-option v-for="item in levels" :key="item.value" v-bind="item"/>
-                    </el-select>
-                </el-form-item>
-                <el-form-item v-if="view === 'dispatch' || view === 'abnormal'" label="异常类型">
-                    <el-select v-model="query.abnormalType" clearable class="filter-select" style="width:160px">
-                        <el-option label="报警" value="alarm"/>
-                        <el-option label="维修" value="repair"/>
-                        <el-option label="离线" value="offline"/>
-                    </el-select>
-                </el-form-item>
-                <el-form-item v-if="view === 'dispatch' || view === 'abnormal'" label="区域">
-                    <el-select v-model="query.region" clearable class="filter-select" style="width:140px">
-                        <el-option label="城南" value="城南"/>
-                        <el-option label="城北" value="城北"/>
-                        <el-option label="太常" value="太常"/>
-                    </el-select>
-                </el-form-item>
-                <el-form-item>
-                    <el-button type="primary" @click="loadData">查询</el-button>
-                    <el-button @click="reset">重置</el-button>
-                </el-form-item>
-            </el-form>
-        </el-card>
-
-        <el-card shadow="never" class="water-panel">
-            <div class="card-title"><span><el-icon><Monitor/></el-icon> {{ title }}清单</span>
-                <div class="panel-actions">
-                    <el-button v-if="view === 'ledger'" type="primary" plain size="small" :icon="Plus"
-                               @click="openCreate">新增
-                    </el-button>
-                    <el-button v-if="view === 'ledger'" type="danger" plain size="small" :icon="Delete"
-                               :disabled="!selectedDevices.length" @click="removeSelectedDevices">删除
-                    </el-button>
-                    <el-button v-if="view === 'ledger'" type="info" plain size="small" :icon="Upload"
-                               @click="openImport">导入
-                    </el-button>
-                    <el-button v-if="view !== 'gis'" type="success" plain size="small" :icon="Download"
-                               @click="exportData">导出
-                    </el-button>
-                </div>
-            </div>
-            <el-alert v-if="loadError" :title="loadError" type="error" show-icon :closable="false" class="load-error">
-                <el-button size="small" type="danger" plain @click="loadData">重试</el-button>
-            </el-alert>
-            <el-empty v-if="view === 'gis' && !loading && !loadError && !page.records.length"
-                      description="暂无设备 GIS 点位数据"/>
-            <el-table :data="page.records" border stripe v-loading="loading" row-key="equipmentId"
-                      @selection-change="handleDeviceSelectionChange">
-                <el-table-column v-if="view === 'ledger'" type="selection" width="48" align="center"/>
-                <el-table-column type="index" label="#" width="55"/>
-                <el-table-column prop="equipmentCode" label="设备编码" min-width="130"/>
-                <el-table-column prop="equipmentName" label="设备名称" min-width="150"/>
-                <el-table-column prop="equipmentTypeName" label="设备类型" min-width="120"/>
-                <el-table-column v-if="view === 'abnormal' || view === 'dispatch'" label="所属区域" width="110">
-                    <template #default="{ row }">{{ row.district || '-' }}</template>
-                </el-table-column>
-                <el-table-column prop="equipmentLocation" label="安装位置" min-width="180" show-overflow-tooltip/>
-                <el-table-column v-if="view === 'ledger' || view === 'abnormal'" label="设备图片" width="110" align="center">
-                    <template #default="{ row }">
-                        <el-image v-if="row.equipmentImageUrl" :src="row.equipmentImageUrl"
-                                  :preview-src-list="[row.equipmentImageUrl]" preview-teleported hide-on-click-modal
-                                  fit="cover" class="device-image"/>
-                        <span v-else class="image-placeholder">暂无图片</span></template>
-                </el-table-column>
-                <el-table-column v-if="view !== 'maintain'" label="设备状态" width="100">
-                    <template #default="{ row }">
-                        <el-tag :type="Number(row.currentStatus) === 3 ? 'danger' : 'success'">{{ row.currentStatusText
-                            }}
-                        </el-tag>
-                    </template>
-                </el-table-column>
-                <el-table-column v-if="view !== 'maintain'" label="在线状态" width="100">
-                    <template #default="{ row }">
-                        <el-tag :type="Number(row.onlineStatus) === 1 ? 'success' : 'info'">{{ row.onlineStatusText }}
-                        </el-tag>
-                    </template>
-                </el-table-column>
-                <el-table-column v-if="view !== 'maintain'" label="报警状态" width="100">
-                    <template #default="{ row }">
-                        <el-tag :type="Number(row.alarmStatus) === 1 ? 'danger' : 'success'">{{ row.alarmStatusText }}
-                        </el-tag>
-                    </template>
-                </el-table-column>
-                <el-table-column v-if="view !== 'maintain'" label="报警等级" width="110">
-                    <template #default="{ row }">
-                        <el-tag :type="alarmLevelType(row.alertLevel)">{{ row.alarmLevelText }}</el-tag>
-                    </template>
-                </el-table-column>
-                <el-table-column v-if="view === 'abnormal' || view === 'dispatch'" label="异常原因" min-width="150"
-                                 show-overflow-tooltip>
-                    <template #default="{ row }">{{ row.abnormalReasonText || '-' }}</template>
-                </el-table-column>
-                <el-table-column label="关联工单" min-width="190" show-overflow-tooltip>
-                    <template #default="{ row }">{{ view === 'maintain' ? (row.orderNo || '-') : row.relatedOrderNo }}
-                    </template>
-                </el-table-column>
-                <el-table-column label="工单状态" min-width="110">
-                    <template #default="{ row }">
-                        <el-tag v-if="view !== 'maintain' && row.workOrderStatusText !== '暂无工单'"
-                                :type="workOrderStatusType(row.workOrderStatusCode)">{{ row.workOrderStatusText }}
-                        </el-tag>
-                        <span v-else>{{ view === 'maintain' ? (row.status || '暂无') : row.workOrderStatusText }}</span>
-                    </template>
-                </el-table-column>
-                <el-table-column v-if="view === 'abnormal' || view === 'dispatch'" label="接单责任人" min-width="110">
-                    <template #default="{ row }">{{ view === 'maintain' ? (row.maintainPerson || '-') :
-                        (row.relatedWorkOrderPerson || '-') }}
-                    </template>
-                </el-table-column>
-                <el-table-column label="操作" width="210" fixed="right">
-                    <template #default="{ row }">
-                        <div class="facility-row-actions">
-                            <el-tooltip content="查看详情" placement="top">
-                                <el-button class="facility-row-action is-detail" aria-label="查看详情" size="small"
-                                           type="primary" plain circle :icon="View" @click="selectPoint(row)"/>
-                            </el-tooltip>
-                            <el-tooltip v-if="view === 'ledger'" content="修改信息" placement="top">
-                                <el-button class="facility-row-action is-edit" aria-label="修改信息" size="small"
-                                           type="warning" plain circle :icon="Edit" @click="openEdit(row)"/>
-                            </el-tooltip>
-                            <el-tooltip v-if="canDispatch(row)" content="维修派单" placement="top">
-                                <el-button class="facility-row-action is-dispatch" aria-label="维修派单" size="small"
-                                           type="warning" plain circle :icon="Position" @click="openOrder(row)"/>
-                            </el-tooltip>
-                            <el-tooltip v-if="view === 'ledger'" content="删除设备" placement="top">
-                                <el-button class="facility-row-action is-delete" aria-label="删除设备" size="small"
-                                           type="danger" plain circle :icon="Delete" @click="removeDevice(row)"/>
-                            </el-tooltip>
-                            <el-tooltip v-if="view === 'maintain'" content="删除记录" placement="top">
-                                <el-button class="facility-row-action is-delete" aria-label="删除记录" size="small"
-                                           type="danger" plain circle :icon="Delete" @click="removeRecord(row)"/>
-                            </el-tooltip>
-                        </div>
-                    </template>
-                </el-table-column>
-            </el-table>
-            <el-pagination v-model:current-page="query.pageNum" v-model:page-size="query.pageSize" :total="page.total"
-                           layout="total, sizes, prev, pager, next" :page-sizes="[10, 20, 50]" @change="loadData"/>
-        </el-card>
-
-        <el-drawer v-model="detailVisible" class="detail-drawer device-detail-drawer" title="设备详情" direction="rtl"
-                   size="680px" append-to-body>
-            <div class="detail-summary"><span class="detail-name">{{ detail.equipmentName || '-' }}</span>
-                <el-tag :type="Number(detail.currentStatus) === 3 ? 'danger' : 'success'">{{ detail.currentStatusText ||
-                    '-' }}
-                </el-tag>
-                <el-tag :type="Number(detail.alarmStatus) === 1 ? 'danger' : 'success'">{{ detail.alarmStatusText || '-'
-                    }}
-                </el-tag>
-                <el-tag v-if="detail.workOrderStatusText && detail.workOrderStatusText !== '暂无工单'"
-                        :type="workOrderStatusType(detail.workOrderStatusCode)">{{ detail.workOrderStatusText }}
-                </el-tag>
-            </div>
-            <div v-if="view === 'ledger' || view === 'abnormal'" class="detail-media">
-                <div class="detail-media-label">设备图片</div>
-                <el-image v-if="detail.equipmentImageUrl" :src="detail.equipmentImageUrl"
-                          :preview-src-list="[detail.equipmentImageUrl]" preview-teleported hide-on-click-modal
-                          fit="cover"
-                          class="detail-image"/>
-                <div v-else class="detail-image-placeholder">暂无图片</div>
-            </div>
-            <el-descriptions :column="2" border>
-                <el-descriptions-item v-for="item in detailFields" :key="item.label" :label="item.label">{{ item.value
-                    }}
-                </el-descriptions-item>
-            </el-descriptions>
-            <template #footer>
-                <el-button @click="detailVisible = false">关闭</el-button>
-            </template>
-        </el-drawer>
-        <el-drawer v-model="editVisible" class="detail-drawer device-edit-drawer"
-                   :title="editMode === 'create' ? '新增设备信息' : '修改设备信息'" direction="rtl" size="680px"
-                   append-to-body>
-            <el-form ref="editFormRef" :model="editForm" label-width="110px" :rules="editRules">
-                <el-row :gutter="16">
-                    <el-col :span="12">
-                        <el-form-item label="设备编码" prop="equipmentCode">
-                            <el-input v-model="editForm.equipmentCode" :disabled="editMode === 'edit'" maxlength="50"/>
-                        </el-form-item>
-                    </el-col>
-                    <el-col :span="12">
-                        <el-form-item label="设备类型" prop="equipmentTypeId">
-                            <el-select v-if="editMode === 'create'" v-model="editForm.equipmentTypeId" filterable
-                                       class="full-input">
-                                <el-option v-for="item in waterDeviceTypes" :key="item.id" :label="item.typeName"
-                                           :value="item.id"/>
-                            </el-select>
-                            <el-input v-else v-model="editForm.equipmentTypeName" disabled/>
-                        </el-form-item>
-                    </el-col>
-                    <el-col :span="12">
-                        <el-form-item label="设备名称" prop="equipmentName">
-                            <el-input v-model="editForm.equipmentName" clearable/>
-                        </el-form-item>
-                    </el-col>
-                    <el-col :span="12">
-                        <el-form-item label="设备型号">
-                            <el-input v-model="editForm.equipmentModel" clearable/>
-                        </el-form-item>
-                    </el-col>
-                    <el-col :span="12">
-                        <el-form-item label="所属片区">
-                            <el-select v-model="editForm.district" clearable class="full-input">
-                                <el-option label="城南" value="城南"/>
-                                <el-option label="城北" value="城北"/>
-                                <el-option label="太常" value="太常"/>
-                            </el-select>
-                        </el-form-item>
-                    </el-col>
-                    <el-col :span="12">
-                        <el-form-item label="制造商">
-                            <el-input v-model="editForm.manufacturer" clearable/>
-                        </el-form-item>
-                    </el-col>
-                    <el-col :span="12">
-                        <el-form-item label="经度">
-                            <el-input-number v-model="editForm.longitude" :precision="8" :controls="false"
-                                             class="full-input"/>
-                        </el-form-item>
-                    </el-col>
-                    <el-col :span="12">
-                        <el-form-item label="纬度">
-                            <el-input-number v-model="editForm.latitude" :precision="8" :controls="false"
-                                             class="full-input"/>
-                        </el-form-item>
-                    </el-col>
-                    <el-col :span="24">
-                        <el-form-item label="安装位置">
-                            <el-input v-model="editForm.equipmentLocation" clearable/>
-                        </el-form-item>
-                    </el-col>
-                    <el-col :span="12">
-                        <el-form-item label="设备负责人">
-                            <el-input v-model="editForm.maintainer" clearable/>
-                        </el-form-item>
-                    </el-col>
-                    <el-col :span="12">
-                        <el-form-item label="联系方式">
-                            <el-input v-model="editForm.maintainerPhone" clearable/>
-                        </el-form-item>
-                    </el-col>
-                    <el-col :span="24">
-                        <el-form-item label="权属单位">
-                            <el-input v-model="editForm.ownershipUnit" clearable/>
-                        </el-form-item>
-                    </el-col>
-                    <el-col :span="24">
-                        <el-form-item label="设备图片">
-                            <ImageUpload v-model="editForm.equipmentImage" :limit="1" :file-size="5"/>
-                        </el-form-item>
-                    </el-col>
-                    <el-col :span="24">
-                        <el-form-item label="备注">
-                            <el-input v-model="editForm.remark" type="textarea" :rows="3" maxlength="500"
-                                      show-word-limit/>
-                        </el-form-item>
-                    </el-col>
-                </el-row>
-            </el-form>
-            <template #footer>
-                <el-button @click="editVisible = false">取消</el-button>
-                <el-button type="primary" :loading="editSaving" @click="submitEdit">保存</el-button>
-            </template>
-        </el-drawer>
-        <el-dialog v-model="dispatchVisible" class="dispatch-dialog" title="设备维修派单" width="860px">
-            <div class="dispatch-layout">
-                <div class="assignee-picker">
-                    <aside class="assignee-depts">
-                        <el-input v-model="deptFilterText" clearable placeholder="筛选部门" size="small"/>
-                        <el-tree ref="deptTreeRef" :data="deptTreeData" node-key="deptId" highlight-current
-                                 :props="{ label: 'deptName', children: 'children' }"
-                                 :filter-node-method="filterDeptNode" class="assignee-tree"
-                                 @node-click="handleDeptClick"/>
-                    </aside>
-                    <section class="assignee-users">
-                        <div class="assignee-search">
-                            <el-input v-model="assigneeKeyword" clearable placeholder="搜索用户名、昵称或手机号"
-                                      @keyup.enter="searchAssigneeUsers"/>
-                            <el-button type="primary" :loading="userLoading" @click="searchAssigneeUsers">查询
-                            </el-button>
-                        </div>
-                        <el-alert v-if="userLoadError" :title="userLoadError" type="error" show-icon :closable="false">
-                            <el-button size="small" type="danger" plain @click="retryAssigneeLoading">重试</el-button>
-                        </el-alert>
-                        <el-table :data="users" border row-key="userId" highlight-current-row
-                                  :current-row-key="assigneeDraft?.userId" @row-click="chooseAssignee">
-                            <el-table-column label="选择" width="58" align="center">
-                                <template #default="{ row }">
-                                    <el-radio :model-value="assigneeDraft?.userId" :label="row.userId"
-                                              @click.stop="chooseAssignee(row)">&nbsp;
-                                    </el-radio>
-                                </template>
-                            </el-table-column>
-                            <el-table-column prop="nickName" label="昵称" min-width="100"/>
-                            <el-table-column prop="userName" label="用户名" min-width="110"/>
-                            <el-table-column prop="deptName" label="部门" min-width="120" show-overflow-tooltip/>
-                            <el-table-column prop="phone" label="手机号" min-width="130"/>
-                            <el-table-column prop="statusText" label="账号状态" width="90"/>
-                        </el-table>
-                        <el-empty v-if="!userLoading && !userLoadError && !users.length"
-                                  description="暂无符合条件的责任人" :image-size="80"/>
-                        <el-pagination v-model:current-page="assigneePageNum" v-model:page-size="assigneePageSize"
-                                       :total="assigneeTotal" layout="total, sizes, prev, pager, next"
-                                       :page-sizes="[10, 20, 50]" @current-change="loadAssigneeUsers"
-                                       @size-change="handleAssigneeSizeChange"/>
-                        <div class="assignee-picked">已选责任人:<strong v-if="assigneeDraft">{{ assigneeDraft.nickName
-                            || assigneeDraft.userName }}</strong><span v-else>未选择</span>
-                            <el-button size="small" type="primary" plain :disabled="!assigneeDraft"
-                                       @click="confirmAssignee">确认选择
-                            </el-button>
-                        </div>
-                    </section>
-                </div>
-                <el-form class="dispatch-form" :model="dispatchForm" label-width="100px">
-                    <el-form-item label="设备"><span>{{ dispatchDevice.equipmentName }}({{ dispatchDevice.equipmentCode }})</span>
-                    </el-form-item>
-                    <el-form-item label="工单类型"><span>1-故障维修</span></el-form-item>
-                    <el-form-item label="优先级" required>
-                        <el-select v-model="dispatchForm.orderLevel" placeholder="请选择优先级">
-                            <el-option label="紧急" :value="0"/>
-                            <el-option label="一般" :value="1"/>
-                            <el-option label="低" :value="2"/>
-                        </el-select>
-                    </el-form-item>
-                    <el-form-item label="问题描述" required>
-                        <el-input v-model="dispatchForm.description" type="textarea" :rows="5" maxlength="500"
-                                  show-word-limit placeholder="请输入故障或异常描述"/>
-                    </el-form-item>
-                    <el-form-item label="接单责任人" required>
-                        <span v-if="selectedAssignee" class="assignee-summary">{{ selectedAssignee.nickName || selectedAssignee.userName }}({{ selectedAssignee.deptName || '-' }})</span>
-                        <span v-else class="assignee-placeholder">请在左侧选择责任人</span>
-                    </el-form-item>
-                </el-form>
-            </div>
-            <template #footer>
-                <el-button @click="dispatchVisible = false">取消</el-button>
-                <el-button type="primary" :loading="dispatching" @click="submitDispatch">确认派单</el-button>
-            </template>
-        </el-dialog>
-
-        <el-dialog v-model="importVisible" title="供水设备批量导入" width="520px" append-to-body @closed="resetImport">
-            <el-upload ref="importUploadRef" drag :limit="1" accept=".xlsx,.xls" :auto-upload="false"
-                       :file-list="importFileList" :on-change="handleImportChange" :on-remove="handleImportRemove"
-                       :disabled="importUploading">
-                <el-icon class="el-icon--upload">
-                    <Upload/>
-                </el-icon>
-                <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
-                <template #tip>
-                    <div class="el-upload__tip import-tip">仅支持 xls、xlsx;设备类型必须属于供水类型树。</div>
-                </template>
-            </el-upload>
-            <el-checkbox v-model="importUpdateSupport" class="import-update">更新已存在的设备编码</el-checkbox>
-            <template #footer>
-                <el-button type="primary" :loading="importUploading" @click="submitImport">开始导入</el-button>
-                <el-button @click="importVisible = false">取消</el-button>
-            </template>
-        </el-dialog>
-    </div>
-</template>
-
-<script setup>
-    import {computed, onMounted, ref, watch} from 'vue'
-    import {ElMessage, ElMessageBox} from 'element-plus'
-    import {
-        Monitor,
-        Refresh,
-        Warning,
-        Bell,
-        Tools,
-        DataLine,
-        Upload,
-        Download,
-        Search,
-        Plus,
-        Delete,
-        View,
-        Edit,
-        Position
-    } from '@element-plus/icons-vue'
-    import {listUser} from '@/api/system/user'
-    import {listDept} from '@/api/system/dept'
-    import {handleTree} from '@/utils/ruoyi'
-    import {
-        createAbnormalOrder,
-        createWaterDevice,
-        deleteMaintainRecord,
-        deleteWaterDevices,
-        exportAbnormalDevices,
-        exportDeviceLedger,
-        getAbnormalDeviceStats,
-        getDeviceLedgerStatistics,
-        getWaterDeviceById,
-        importDeviceLedger,
-        listAbnormalDevices,
-        listDeviceGisPoints,
-        listDeviceLedger,
-        listMaintainRecords,
-        updateWaterDevice
-    } from '@/api/pipeNetwork/waterSupply'
-    import {listEquipmentTypeAll} from '@/api/pipeNetwork/pdeviceInform'
-    import {normalizePage, readMonitoringExportError} from '@/utils/waterSupplyModel'
-    import {
-        buildAssigneeQuery,
-        buildMaintainOrderPayload,
-        canDispatchDevice,
-        normalizeAssigneeUser,
-        normalizeDeviceGisPoints,
-        normalizeDeviceRow,
-        normalizeDispatchOrderResponse,
-        preserveDevicePageOnError,
-        selectSingleAssignee
-    } from '@/utils/waterSupplyUiModel'
-    import {saveAs} from 'file-saver'
-    import {orderStatusType as workOrderStatusType} from '@/views/subSystem/pipeNetwork/ops/workOrder/workOrderDict.js'
-
-    const props = defineProps({
-        title: {type: String, required: true},
-        subtitle: {type: String, default: '设备信息、运行状态与维护过程管理'},
-        view: {type: String, default: 'ledger'}
-    })
-    const {title, subtitle, view} = props
-    const query = ref({
-        pageNum: 1,
-        pageSize: 10,
-        equipmentName: '',
-        equipmentCode: '',
-        alarmStatus: undefined,
-        alertLevel: undefined,
-        abnormalType: undefined,
-        region: undefined
-    })
-    const importVisible = ref(false);
-    const importUploading = ref(false);
-    const importUpdateSupport = ref(false);
-    const importFileList = ref([]);
-    const importUploadRef = ref(null);
-    const editVisible = ref(false);
-    const editSaving = ref(false);
-    const editFormRef = ref(null);
-    const editForm = ref({})
-    const editMode = ref('edit');
-    const selectedDevices = ref([]);
-    const waterDeviceTypes = ref([])
-    const editRules = {
-        equipmentCode: [{required: true, message: '请输入设备编码', trigger: 'blur'}],
-        equipmentTypeId: [{required: true, message: '请选择设备类型', trigger: 'change'}],
-        equipmentName: [{required: true, message: '请输入设备名称', trigger: 'blur'}]
-    }
-    const page = ref({records: [], total: 0});
-    const ledgerStats = ref({total: 0, online: 0, offline: 0, onlineRate: 0});
-    const abnormalStatsData = ref({total: 0, alarm: 0, repair: 0, offline: 0});
-    const loading = ref(false);
-    const loadError = ref('');
-    const detailVisible = ref(false);
-    const detail = ref({});
-    const dispatchVisible = ref(false);
-    const dispatching = ref(false);
-    const dispatchDevice = ref({});
-    const dispatchForm = ref({description: '', receiveUserId: undefined});
-    const users = ref([]);
-    const userLoading = ref(false);
-    const userLoadError = ref('');
-    const deptLoadError = ref('');
-    const selectedAssignee = ref(null);
-    const assigneeDraft = ref(null);
-    const deptTreeRef = ref(null);
-    const deptTreeData = ref([]);
-    const deptFilterText = ref('');
-    const assigneeKeyword = ref('');
-    const assigneeDeptId = ref(undefined);
-    const assigneePageNum = ref(1);
-    const assigneePageSize = ref(10);
-    const assigneeTotal = ref(0)
-    const levels = [{label: '一级报警', value: 1}, {label: '二级报警', value: 2}, {
-        label: '三级报警',
-        value: 3
-    }, {label: '四级报警', value: 4}];
-    const pageIcon = Monitor;
-    const alarmLevelType = level => ({1: 'danger', 2: 'warning', 3: 'primary', 4: 'success'}[Number(level)] || 'info')
-
-    function canDispatch(row) {
-        if (view !== 'dispatch') return false
-        return canDispatchDevice(row)
-    }
-
-
-    const abnormalStats = computed(() => {
-        return [
-            {label: '异常设备', value: abnormalStatsData.value.total, unit: '台', icon: Warning},
-            {label: '报警中', value: abnormalStatsData.value.alarm, unit: '台', icon: Bell},
-            {label: '维修中', value: abnormalStatsData.value.repair, unit: '台', icon: Tools},
-            {label: '离线设备', value: abnormalStatsData.value.offline, unit: '台', icon: DataLine}
-        ]
-    })
-    const ledgerStatsCards = computed(() => [
-        {label: '设备数量', value: ledgerStats.value.total, unit: '台', icon: Monitor},
-        {label: '在线设备', value: ledgerStats.value.online, unit: '台', icon: DataLine},
-        {label: '离线设备', value: ledgerStats.value.offline, unit: '台', icon: Warning},
-        {label: '在线率', value: ledgerStats.value.onlineRate, unit: '%', icon: Tools}
-    ])
-
-    const abnormalViews = ['abnormal', 'dispatch']
-    const detailFields = computed(() => [
-        ['设备编码', detail.value.equipmentCode],
-        ['设备类型', detail.value.equipmentTypeName || detail.value.typeName],
-        ...(abnormalViews.includes(view) ? [['所属区域', detail.value.district]] : []),
-        ['安装位置', detail.value.equipmentLocation],
-        ['设备状态', detail.value.currentStatusText],
-        ['在线状态', detail.value.onlineStatusText],
-        ['报警状态', detail.value.alarmStatusText],
-        ['报警等级', detail.value.alarmLevelText],
-        ['异常原因', detail.value.abnormalReasonText],
-        ['关联工单', detail.value.relatedOrderNo || detail.value.orderNo],
-        ['工单状态', detail.value.workOrderStatusText || detail.value.status],
-        ['接单责任人', detail.value.relatedWorkOrderPerson || detail.value.maintainPerson],
-        ['状态更新时间', detail.value.statusUpdateTime]
-    ].map(([label, value]) => ({
-        label,
-        value: value ?? '-'
-    })))
-
-    async function loadData() {
-        loading.value = true;
-        loadError.value = '';
-        try {
-            if (view === 'gis') {
-                const response = await listDeviceGisPoints({...query.value});
-                const records = normalizeDeviceGisPoints(response).map(normalizeDeviceRow);
-                page.value = {records, total: records.length, current: 1, size: records.length || 10};
-                return
-            }
-            let response;
-            if (view === 'maintain') response = await listMaintainRecords({...query.value}); else if (view === 'dispatch' || view === 'abnormal') response = await listAbnormalDevices({...query.value, ...(view === 'dispatch' ? {dispatchableOnly: true} : {})}); else response = await listDeviceLedger({...query.value});
-            const normalized = normalizePage(response);
-            page.value = {...normalized, records: normalized.records.map(normalizeDeviceRow)}
-            if (view === 'ledger') await loadLedgerStats()
-            if (view === 'abnormal') await loadAbnormalStats()
-        } catch (error) {
-            const retained = preserveDevicePageOnError(page.value);
-            page.value = retained.page;
-            loadError.value = retained.error;
-            console.warn(`[${title}] 数据加载失败`, error)
-        } finally {
-            loading.value = false
-        }
-    }
-
-    async function loadLedgerStats() {
-        try {
-            const response = await getDeviceLedgerStatistics()
-            const stats = response?.data ?? response ?? {}
-            ledgerStats.value = {
-                total: Number(stats.total ?? 0) || 0,
-                online: Number(stats.online ?? 0) || 0,
-                offline: Number(stats.offline ?? 0) || 0,
-                onlineRate: Number(stats.onlineRate ?? 0) || 0
-            }
-        } catch (error) {
-            console.warn('设备台账统计加载失败', error)
-        }
-    }
-
-    async function loadAbnormalStats() {
-        try {
-            const response = await getAbnormalDeviceStats()
-            const stats = response?.data ?? response ?? {}
-            abnormalStatsData.value = {
-                total: Number(stats.total ?? 0) || 0,
-                alarm: Number(stats.alarm ?? 0) || 0,
-                repair: Number(stats.repair ?? 0) || 0,
-                offline: Number(stats.offline ?? 0) || 0
-            }
-        } catch (error) {
-            console.warn('[设备异常管理] 统计加载失败', error)
-        }
-    }
-
-    function reset() {
-        query.value = {
-            pageNum: 1,
-            pageSize: 10,
-            equipmentName: '',
-            equipmentCode: '',
-            alarmStatus: undefined,
-            alertLevel: undefined,
-            abnormalType: undefined,
-            region: undefined
-        };
-        loadData()
-    }
-
-    function selectPoint(row) {
-        detail.value = normalizeDeviceRow(row);
-        detailVisible.value = true
-    }
-
-    watch(deptFilterText, value => deptTreeRef.value?.filter(value))
-
-    function filterDeptNode(value, data) {
-        return !value || String(data.deptName || '').includes(value)
-    }
-
-    async function loadDeptTree(force = false) {
-        if (!force && deptTreeData.value.length) return true;
-        deptLoadError.value = '';
-        try {
-            const response = await listDept();
-            deptTreeData.value = handleTree(response.data || [], 'deptId');
-            return true
-        } catch (error) {
-            deptLoadError.value = '部门列表加载失败,请点击重试';
-            userLoadError.value = deptLoadError.value;
-            return false
-        }
-    }
-
-    async function loadAssigneeUsers() {
-        userLoading.value = true;
-        userLoadError.value = '';
-        try {
-            const response = await listUser(buildAssigneeQuery({
-                deptId: assigneeDeptId.value,
-                keyword: assigneeKeyword.value,
-                pageNum: assigneePageNum.value,
-                pageSize: assigneePageSize.value
-            }));
-            const rows = response.rows || response.data?.rows || [];
-            users.value = rows.map(normalizeAssigneeUser).filter(user => user.eligible);
-            assigneeTotal.value = Number(response.total ?? response.data?.total ?? users.value.length) || 0
-        } catch (error) {
-            userLoadError.value = '责任人列表加载失败,请点击重试'
-        } finally {
-            userLoading.value = false
-        }
-    }
-
-    function searchAssigneeUsers() {
-        assigneePageNum.value = 1;
-        loadAssigneeUsers()
-    }
-
-    function handleAssigneeSizeChange() {
-        assigneePageNum.value = 1;
-        loadAssigneeUsers()
-    }
-
-    async function retryAssigneeLoading() {
-        const deptLoaded = await loadDeptTree(true);
-        if (!deptLoaded) return;
-        await loadAssigneeUsers()
-    }
-
-    async function openOrder(row) {
-        dispatchDevice.value = row
-        dispatchForm.value = {
-            description: row.abnormalReason || row.abnormalReasonText || row.description || '',
-            orderLevel: 1,
-            receiveUserId: undefined
-        }
-        selectedAssignee.value = null
-        assigneeDraft.value = null
-        assigneeDeptId.value = undefined
-        assigneeKeyword.value = ''
-        assigneePageNum.value = 1
-        dispatchVisible.value = true
-        await retryAssigneeLoading()
-    }
-
-    function handleDeptClick(node) {
-        assigneeDeptId.value = node.deptId;
-        assigneePageNum.value = 1;
-        loadAssigneeUsers()
-    }
-
-    function chooseAssignee(row) {
-        assigneeDraft.value = selectSingleAssignee([row])
-    }
-
-    function confirmAssignee() {
-        selectedAssignee.value = assigneeDraft.value;
-        dispatchForm.value.receiveUserId = assigneeDraft.value?.userId
-    }
-
-    async function submitDispatch() {
-        if (!dispatchForm.value.description?.trim()) return ElMessage.warning('请填写问题描述')
-        if (!dispatchForm.value.receiveUserId) return ElMessage.warning('请选择接单责任人')
-        dispatching.value = true
-        try {
-            const response = await createAbnormalOrder(buildMaintainOrderPayload(dispatchDevice.value, dispatchForm.value))
-            const summary = normalizeDispatchOrderResponse(response)
-            if (!summary) throw new Error('派单失败:服务端未返回有效的待接单工单')
-            ElMessage.success(`已派单,等待接单(${summary.orderNo})`)
-            dispatchVisible.value = false
-            await loadData()
-        } catch (error) {
-            const responseData = error?.response?.data
-            const message = responseData?.msg || responseData?.message || error?.message || '派单失败,请检查权限或服务状态'
-            // A conflict means another order now owns the device; refresh the list so its order number is visible.
-            if (/已有|冲突|重复|处理中的工单|关联工单/.test(String(message))) {
-                await loadData()
-                ElMessage.warning(`该设备已有相关处置工单:${message}`)
-            } else {
-                ElMessage.error(message)
-            }
-        } finally {
-            dispatching.value = false
-        }
-    }
-
-    async function removeRecord(row) {
-        try {
-            await deleteMaintainRecord(row.maintainId || row.id);
-            ElMessage.success('记录已删除');
-            await loadData()
-        } catch (error) {
-            ElMessage.error('删除失败')
-        }
-    }
-
-    function emptyDeviceForm() {
-        return {
-            equipmentId: undefined, equipmentCode: '', equipmentTypeId: undefined, equipmentTypeName: '',
-            equipmentName: '', equipmentModel: '', district: '', manufacturer: '', longitude: undefined,
-            latitude: undefined, equipmentLocation: '', maintainer: '', maintainerPhone: '', ownershipUnit: '',
-            equipmentImage: '', remark: ''
-        }
-    }
-
-    function isWaterDeviceType(type, typesById) {
-        if (!type || (type.typeName === '供水' && String(type.parentId) === '0')) return false
-        let cursor = type
-        let guard = 0
-        while (cursor && guard++ <= typesById.size) {
-            if (cursor.typeName === '供水' && String(cursor.parentId) === '0') return true
-            cursor = typesById.get(cursor.parentId)
-        }
-        return false
-    }
-
-    async function loadWaterTypes() {
-        if (waterDeviceTypes.value.length) return
-        try {
-            const response = await listEquipmentTypeAll()
-            const rows = response?.data || []
-            const typesById = new Map(rows.map(item => [item.id, item]))
-            waterDeviceTypes.value = rows.filter(item => isWaterDeviceType(item, typesById))
-                .map(({id, typeName}) => ({id, typeName}))
-        } catch (error) {
-            ElMessage.warning('供水设备类型加载失败,请重试')
-        }
-    }
-
-    async function openCreate() {
-        editMode.value = 'create'
-        editVisible.value = true
-        editSaving.value = false
-        editForm.value = emptyDeviceForm()
-        await loadWaterTypes()
-    }
-
-    function handleDeviceSelectionChange(rows) {
-        selectedDevices.value = rows
-    }
-
-    async function confirmDeleteDevices(ids) {
-        await ElMessageBox.confirm(`确定删除选中的 ${ids.length} 台设备吗?删除后不可恢复。`, '删除设备', {type: 'warning'})
-        await deleteWaterDevices(ids)
-        ElMessage.success('删除成功')
-        selectedDevices.value = []
-        query.value.pageNum = 1
-        await loadData()
-    }
-
-    async function removeDevice(row) {
-        if (!row?.equipmentId) return
-        await confirmDeleteDevices([row.equipmentId])
-    }
-
-    async function removeSelectedDevices() {
-        const ids = selectedDevices.value.map(row => row.equipmentId).filter(Boolean)
-        if (!ids.length) return
-        await confirmDeleteDevices(ids)
-    }
-
-    async function openEdit(row) {
-        editMode.value = 'edit'
-        editVisible.value = true
-        editSaving.value = false
-        editForm.value = {...row}
-        try {
-            const response = await getWaterDeviceById(row.equipmentId)
-            const full = response?.data || response
-            if (full) editForm.value = {...editForm.value, ...full}
-        } catch (error) {
-            ElMessage.warning('设备详情加载失败,已使用列表数据填充')
-        }
-    }
-
-    async function submitEdit() {
-        try {
-            await editFormRef.value?.validate()
-        } catch {
-            return
-        }
-        editSaving.value = true
-        try {
-            const creating = editMode.value === 'create'
-            if (creating) await createWaterDevice(editForm.value)
-            else await updateWaterDevice(editForm.value)
-            ElMessage.success(creating ? '新增成功' : '修改成功')
-            editVisible.value = false
-            if (creating) query.value.pageNum = 1
-            await loadData()
-        } catch (error) {
-            ElMessage.error(error?.response?.data?.msg || error?.message || (editMode.value === 'create' ? '新增失败,请稍后重试' : '修改失败,请稍后重试'))
-        } finally {
-            editSaving.value = false
-        }
-    }
-
-    function openImport() {
-        importVisible.value = true
-    }
-
-    function handleImportChange(file, fileList) {
-        if (!/\.(xlsx|xls)$/iu.test(file?.name || '')) {
-            ElMessage.error('仅支持 xls、xlsx 文件')
-            importFileList.value = []
-            return false
-        }
-        importFileList.value = fileList.slice(-1)
-    }
-
-    function handleImportRemove(file, fileList) {
-        importFileList.value = fileList
-    }
-
-    function resetImport() {
-        importUploading.value = false
-        importUpdateSupport.value = false
-        importFileList.value = []
-        importUploadRef.value?.clearFiles()
-    }
-
-    async function submitImport() {
-        if (!importFileList.value.length) return ElMessage.warning('请选择 xls 或 xlsx 文件')
-        importUploading.value = true
-        try {
-            const response = await importDeviceLedger(importFileList.value[0].raw, importUpdateSupport.value)
-            if (response?.code && response.code !== 200) throw new Error(response.msg || response.message || '导入失败,请检查文件内容')
-            ElMessage.success(response?.msg || response?.data || '导入成功')
-            importVisible.value = false
-            await loadData()
-        } catch (error) {
-            ElMessage.error(error?.response?.data?.msg || error?.response?.data?.message || error?.message || '导入失败,请稍后重试')
-        } finally {
-            importUploading.value = false
-        }
-    }
-
-    async function exportData() {
-        try {
-            const response = await (view === 'dispatch' || view === 'abnormal' ? exportAbnormalDevices : exportDeviceLedger)({...query.value})
-            const errorMessage = await readMonitoringExportError(response)
-            if (errorMessage) throw new Error(errorMessage)
-            const blob = response instanceof Blob ? response : new Blob([response], {type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'})
-            saveAs(blob, `${title}_${new Date().toISOString().slice(0, 10)}.xlsx`)
-            ElMessage.success('导出成功')
-        } catch (error) {
-            const backendMessage = await readMonitoringExportError(error?.response?.data)
-            ElMessage.error(backendMessage || error?.message || '导出失败,请稍后重试')
-        }
-    }
-
-    onMounted(loadData)
-</script>
-
-<style scoped>
-    .device-page {
-        padding: 20px;
-        background: linear-gradient(135deg, #eff6ff 0%, #f8fafc 45%, #eef2f7 100%);
-        min-height: calc(100vh - 84px)
-    }
-
-    .page-heading {
-        display: flex;
-        justify-content: space-between;
-        align-items: center;
-        gap: 16px;
-        margin-bottom: 16px
-    }
-
-    .page-title {
-        display: flex;
-        align-items: center;
-        gap: 12px;
-        min-width: 0
-    }
-
-    .page-title-icon {
-        display: grid;
-        place-items: center;
-        width: 42px;
-        height: 42px;
-        color: #fff;
-        background: linear-gradient(135deg, #2563eb 0%, #06b6d4 100%);
-        border-radius: 8px;
-        box-shadow: 0 8px 16px rgba(37, 99, 235, .2)
-    }
-
-    .page-title h2 {
-        margin: 0 0 4px;
-        font-size: 20px;
-        color: #1e293b
-    }
-
-    .muted {
-        font-size: 13px;
-        color: #64748b
-    }
-
-    .heading-refresh {
-        border-color: #bfdbfe;
-        color: #2563eb;
-        background: #fff
-    }
-
-    .filter-card, .water-panel {
-        border: 1px solid #e2e8f0;
-        border-radius: 8px;
-        box-shadow: 0 6px 14px rgba(15, 23, 42, .04)
-    }
-
-    .filter-card {
-        margin-bottom: 16px;
-        background: #fff
-    }
-
-    .filter-card :deep(.el-card__header) {
-        padding: 14px 18px;
-        border-bottom: 1px solid #e2e8f0;
-        background: #fbfdff
-    }
-
-    .filter-card :deep(.el-form-item) {
-        margin-top: 8px;
-        margin-right: 16px;
-        margin-bottom: 8px
-    }
-
-    .filter-card :deep(.el-form-item__label) {
-        color: #475569
-    }
-
-    .metric-grid {
-        display: grid;
-        grid-template-columns:repeat(4, minmax(0, 1fr));
-        gap: 14px;
-        margin-bottom: 16px
-    }
-
-    .metric {
-        display: flex;
-        align-items: center;
-        gap: 14px;
-        min-width: 0;
-        padding: 16px 18px;
-        background: #fff;
-        border: 1px solid #e2e8f0;
-        border-radius: 8px;
-        box-shadow: 0 6px 14px rgba(15, 23, 42, .04)
-    }
-
-    .metric-icon {
-        position: static;
-        flex: 0 0 42px;
-        display: grid;
-        place-items: center;
-        width: 42px;
-        height: 42px;
-        color: #2563eb;
-        background: linear-gradient(135deg, #dbeafe 0%, #eff6ff 100%);
-        border-radius: 8px
-    }
-
-    .metric-icon .el-icon {
-        font-size: 22px
-    }
-
-    .metric-body {
-        min-width: 0;
-        text-align: left
-    }
-
-    .metric-body span {
-        display: block;
-        overflow: hidden;
-        margin-bottom: 3px;
-        color: #64748b;
-        font-size: 13px;
-        text-overflow: ellipsis;
-        white-space: nowrap
-    }
-
-    .metric-value {
-        display: flex;
-        align-items: baseline;
-        gap: 5px;
-        min-width: 0
-    }
-
-    .metric-value strong {
-        font-size: 24px;
-        line-height: 1.2;
-        color: #1e293b
-    }
-
-    .metric-value small {
-        color: #94a3b8
-    }
-
-    .water-panel {
-        overflow: hidden
-    }
-
-    .water-panel :deep(.el-card__header) {
-        padding: 14px 18px;
-        border-bottom: 1px solid #e2e8f0;
-        background: #fbfdff
-    }
-
-    .card-title {
-        display: flex;
-        align-items: center;
-        gap: 8px;
-        color: #1e293b;
-        font-weight: 600;
-        margin-bottom: 16px
-    }
-
-    .card-title .el-icon {
-        color: #2563eb;
-        font-size: 18px
-    }
-
-    .table-toolbar {
-        display: flex;
-        justify-content: space-between;
-        align-items: center
-    }
-
-    .panel-actions {
-        display: flex;
-        align-items: center;
-        gap: 8px
-    }
-
-    .load-error {
-        margin-bottom: 14px
-    }
-
-    .device-page :deep(.el-table) {
-        --el-table-border-color: #e2e8f0;
-        --el-table-header-bg-color: #f8fafc;
-        --el-table-header-text-color: #475569;
-        --el-table-row-hover-bg-color: #eff6ff
-    }
-
-    .device-page :deep(.el-table__row) {
-        cursor: pointer
-    }
-
-    .device-image {
-        width: 66px;
-        height: 44px;
-        border-radius: 6px;
-        overflow: hidden
-    }
-
-    .image-placeholder {
-        display: inline-grid;
-        place-items: center;
-        width: 66px;
-        height: 44px;
-        color: #94a3b8;
-        font-size: 12px;
-        background: #f1f5f9;
-        border-radius: 6px
-    }
-
-    .detail-summary {
-        display: flex;
-        align-items: center;
-        gap: 12px;
-        margin-bottom: 16px
-    }
-
-    .detail-name {
-        font-size: 18px;
-        font-weight: 700;
-        color: #17324d
-    }
-
-    .device-detail-drawer :deep(.el-drawer__body), .device-edit-drawer :deep(.el-drawer__body) {
-        overflow: auto;
-        padding: 20px 24px
-    }
-
-    .device-detail-drawer :deep(.el-descriptions__label) {
-        white-space: nowrap
-    }
-
-    .device-detail-drawer :deep(.el-descriptions__content) {
-        overflow-wrap: anywhere;
-        word-break: break-word
-    }
-
-    .detail-media {
-        margin-bottom: 14px;
-        padding: 12px;
-        background: #f7fbff;
-        border: 1px solid rgba(37, 99, 235, .1);
-        border-radius: 10px
-    }
-
-    .detail-media-label {
-        margin-bottom: 8px;
-        font-weight: 600;
-        color: #17324d
-    }
-
-    .detail-image {
-        width: 100%;
-        height: 180px;
-        border-radius: 8px;
-        overflow: hidden
-    }
-
-    .detail-image-placeholder {
-        display: grid;
-        place-items: center;
-        height: 120px;
-        background: #f1f5f9;
-        border-radius: 8px;
-        color: #909399
-    }
-
-    .full-input {
-        width: 100%
-    }
-
-    .import-update {
-        margin-top: 10px
-    }
-
-    .import-tip {
-        line-height: 1.6
-    }
-
-    .el-pagination {
-        justify-content: flex-end;
-        margin-top: 16px
-    }
-
-    .dispatch-layout {
-        display: flex;
-        gap: 20px;
-        min-height: 420px
-    }
-
-    .dispatch-form {
-        width: 300px;
-        min-width: 280px;
-        padding-left: 20px;
-        border-left: 1px solid #e4e7ed
-    }
-
-    .assignee-field {
-        display: flex;
-        align-items: center;
-        gap: 12px;
-        width: 100%;
-        min-height: 32px
-    }
-
-    .assignee-summary {
-        color: #17324d
-    }
-
-    .assignee-placeholder {
-        color: #909399
-    }
-
-    .assignee-picker {
-        display: flex;
-        gap: 16px;
-        flex: 1;
-        min-width: 0;
-        min-height: 390px
-    }
-
-    .assignee-depts {
-        width: 190px;
-        min-width: 190px;
-        padding-right: 16px;
-        border-right: 1px solid #e4e7ed
-    }
-
-    .assignee-tree {
-        margin-top: 10px;
-        max-height: 340px;
-        overflow: auto
-    }
-
-    .assignee-users {
-        flex: 1;
-        min-width: 0
-    }
-
-    .assignee-search {
-        display: flex;
-        gap: 8px;
-        margin-bottom: 12px
-    }
-
-    .assignee-search .el-input {
-        flex: 1
-    }
-
-    .assignee-users .el-pagination {
-        margin-top: 12px
-    }
-
-    .assignee-picked {
-        display: flex;
-        justify-content: space-between;
-        align-items: center;
-        gap: 8px;
-        margin-top: 14px;
-        padding: 10px 12px;
-        background: #f5f7fa;
-        border: 1px solid #ebeef5;
-        color: #606266
-    }
-
-    .assignee-picked strong {
-        color: #2563eb
-    }
-
-    .assignee-users .el-alert {
-        margin-bottom: 12px
-    }
-
-    @media (max-width: 900px) {
-        .metric-grid {
-            grid-template-columns:repeat(2, minmax(0, 1fr))
-        }
-
-        .filter-card :deep(.el-form-item) {
-            margin-bottom: 12px
-        }
-    }
-
-    @media (max-width: 768px) {
-        .device-detail-drawer, .device-edit-drawer {
-            width: 100% !important
-        }
-
-        .dispatch-dialog {
-            width: calc(100% - 24px) !important;
-            margin-left: auto;
-            margin-right: auto
-        }
-
-        .dispatch-layout {
-            flex-direction: column
-        }
-
-        .assignee-picker {
-            flex-direction: column
-        }
-
-        .assignee-depts {
-            width: auto;
-            min-width: 0;
-            padding-right: 0;
-            padding-bottom: 12px;
-            border-right: 0;
-            border-bottom: 1px solid #e4e7ed
-        }
-
-        .assignee-tree {
-            max-height: 180px
-        }
-
-        .dispatch-form {
-            width: auto;
-            min-width: 0;
-            padding-left: 0;
-            padding-top: 16px;
-            border-left: 0;
-            border-top: 1px solid #e4e7ed
-        }
-    }
-</style>
-<style lang="scss">
-    @use './waterSupplyPageTheme.scss';
-    @media (max-width: 768px) {
-        .device-detail-drawer {
-            width: 100% !important
-        }
-
-        .dispatch-dialog {
-            width: calc(100% - 24px) !important;
-            margin-left: auto;
-            margin-right: auto
-        }
-
-        .dispatch-layout {
-            flex-direction: column
-        }
-
-        .assignee-picker {
-            flex-direction: column
-        }
-
-        .assignee-depts {
-            width: auto;
-            min-width: 0;
-            padding-right: 0;
-            padding-bottom: 12px;
-            border-right: 0;
-            border-bottom: 1px solid #e4e7ed
-        }
-
-        .assignee-tree {
-            max-height: 180px
-        }
-
-        .dispatch-form {
-            width: auto;
-            min-width: 0;
-            padding-left: 0;
-            padding-top: 16px;
-            border-left: 0;
-            border-top: 1px solid #e4e7ed
-        }
-    }
-</style>

+ 2 - 2
src/views/subSystem/waterSupply/components/WaterSupplyHome.vue

@@ -10,11 +10,11 @@ import {
   getDeviceGisAllPoints,
   getDeviceGisStatistics,
   listRealtimeAlarms,
-  listDeviceLedger,
   listFlowMonitoring,
   listPressureMonitoring,
   listLeakageMonitoring
 } from '@/api/pipeNetwork/waterSupply'
+import { listEquipment } from '@/api/pipeNetwork/pdeviceInform'
 import { normalizeGeoJson, normalizeMonitoringRecord, normalizePage, unwrapResponse } from '@/utils/waterSupplyModel'
 
 const nowText = ref('')
@@ -337,7 +337,7 @@ async function loadDashboardData() {
       getDeviceGisStatistics(),
       listRealtimeAlarms(),
       getAlarmStats(),
-      listDeviceLedger({ pageNum: 1, pageSize: 8 }),
+      listEquipment({ pageNum: 1, pageSize: 8, rootTypeId: 1 }),
       listFlowMonitoring({ pageNum: 1, pageSize: 5 }),
       listPressureMonitoring({ pageNum: 1, pageSize: 5 }),
       listLeakageMonitoring({ pageNum: 1, pageSize: 5 })

+ 0 - 11
src/views/subSystem/waterSupply/device/abnormal.vue

@@ -1,11 +0,0 @@
-<script setup>
-import Page from '../components/WaterDevicePage.vue'
-</script>
-
-<template>
-  <Page
-    title="设备异常管理"
-    view="abnormal"
-    subtitle="异常态势、处置跟踪与关联工单管理"
-  />
-</template>

+ 0 - 1
src/views/subSystem/waterSupply/device/ledger.vue

@@ -1 +0,0 @@
-<script setup>import Page from '../components/WaterDevicePage.vue'</script><template><Page title="设备台账管理" view="ledger" /></template>

+ 0 - 1
src/views/subSystem/waterSupply/device/maintain.vue

@@ -1 +0,0 @@
-<script setup>import Page from '../components/WaterDevicePage.vue'</script><template><Page title="设备维修派单" view="dispatch" subtitle="待派单设备队列与责任人派发" /></template>

+ 0 - 1
src/views/subSystem/waterSupply/device/record.vue

@@ -1 +0,0 @@
-<script setup>import Page from '../components/WaterDevicePage.vue'</script><template><Page title="设备维修记录" view="maintain" /></template>

部分文件因文件數量過多而無法顯示