ソースを参照

fix:燃气管网bug修复

null 2 週間 前
コミット
2ca7ca23da

+ 12 - 3
src/api/pipeNetwork/basic.js

@@ -19,6 +19,9 @@ export function updatePipeNetwork(data) {
 export function deletePipeNetwork(id) {
   return request({ url: '/api/pipe-network/deleteById', method: 'delete', params: { id } })
 }
+export function exportPipeNetwork(params) {
+  return request({ url: '/api/pipe-network/export', method: 'get', params, responseType: 'blob' })
+}
 
 // ============ 窨井管理 ============
 export function getManholePage(pageNum, pageSize, params) {
@@ -91,13 +94,16 @@ export function getEquipmentById(id) {
   return request({ url: '/EquipmentBase/getById/' + id, method: 'get' })
 }
 export function addEquipment(data) {
-  return request({ url: '/EquipmentBase/save', method: 'post', data })
+  return request({ url: '/api/equipment/save', method: 'post', data })
 }
 export function updateEquipment(data) {
-  return request({ url: '/EquipmentBase/updateById', method: 'put', data })
+  return request({ url: '/api/equipment/updateById', method: 'put', data })
 }
 export function deleteEquipment(id) {
-  return request({ url: '/EquipmentBase/deleteById', method: 'delete', params: { id } })
+  return request({ url: '/api/equipment/deleteById', method: 'delete', params: { id } })
+}
+export function getEquipmentTypeList() {
+  return request({ url: '/EquipmentType/getList', method: 'get' })
 }
 export function getEquipmentStatus(equipmentId) {
   return request({ url: '/api/equipment/status/' + equipmentId, method: 'get' })
@@ -118,6 +124,9 @@ 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 })
 }

+ 90 - 13
src/views/subSystem/basic/Equipment.vue

@@ -118,7 +118,7 @@
     </el-dialog>
 
     <el-dialog :title="dialogTitle" v-model="dialogVisible" width="1100px" destroy-on-close>
-      <el-form :model="formData" ref="formRef" label-width="110px" size="default">
+      <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="equipmentCode">
@@ -144,16 +144,46 @@
           </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.manufacturer" />
             </el-form-item>
           </el-col>
+        </el-row>
+        <el-row :gutter="28">
           <el-col :span="12">
             <el-form-item label="安装位置">
               <el-input v-model="formData.equipmentLocation" />
             </el-form-item>
           </el-col>
+          <el-col :span="12">
+            <el-form-item label="维护人员">
+              <el-input v-model="formData.maintainer" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row :gutter="28">
+          <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-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-row>
         <el-row :gutter="28">
           <el-col :span="12">
@@ -168,11 +198,6 @@
           </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-input v-model="formData.maintainerPhone" />
@@ -194,7 +219,7 @@
 <script setup>
 import { ref, reactive, onMounted } from 'vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
-import { getEquipmentFullPage, getEquipmentById, addEquipment, updateEquipment, deleteEquipment } from '@/api/pipeNetwork/basic'
+import { getEquipmentFullPage, addEquipment, updateEquipment, deleteEquipment, getEquipmentTypeList, getPipeNetworkOptions, getGasPipePointPage, saveEquipmentDeviceRel } from '@/api/pipeNetwork/basic'
 
 const loading = ref(false)
 const tableData = ref([])
@@ -205,11 +230,20 @@ const searchForm = reactive({ equipmentCode: '', equipmentName: '', equipmentMod
 const dialogVisible = ref(false)
 const dialogTitle = ref('新增')
 const formRef = ref(null)
+const rules = {
+  equipmentCode: [{ required: true, message: '请输入设备编码', trigger: 'blur' }],
+  equipmentName: [{ required: true, message: '请输入设备名称', trigger: 'blur' }],
+  equipmentTypeId: [{ required: true, message: '请选择设备类别', trigger: 'change' }]
+}
 const detailVisible = ref(false)
 const detailData = ref(null)
+const equipmentTypeOptions = ref([])
+const networkOptions = ref([])
+const pointOptions = ref([])
 const formData = reactive({
   equipmentId: '', equipmentCode: '', equipmentName: '', equipmentModel: '',
-  equipmentSpec: '', manufacturer: '', equipmentLocation: '',
+  equipmentSpec: '', manufacturer: '', equipmentLocation: '', equipmentTypeId: '',
+  networkId: '', pointId: '',
   longitude: null, latitude: null, maintainer: '', maintainerPhone: '', remark: ''
 })
 
@@ -226,18 +260,61 @@ function handleReset() {
   Object.assign(searchForm, { equipmentCode: '', equipmentName: '', equipmentModel: '', manufacturer: '' })
   handleSearch()
 }
+async function loadEquipmentTypes() {
+  if (equipmentTypeOptions.value.length > 0) return
+  try {
+    const res = await getEquipmentTypeList()
+    const all = res.data || []
+    equipmentTypeOptions.value = all.filter(t => t.parentTypeId === '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 handleAdd() {
   dialogTitle.value = '新增'
-  Object.assign(formData, { equipmentId: '', equipmentCode: '', equipmentName: '', equipmentModel: '', equipmentSpec: '', manufacturer: '', equipmentLocation: '', longitude: null, latitude: null, maintainer: '', maintainerPhone: '', remark: '' })
+  Object.assign(formData, { equipmentId: '', equipmentCode: '', equipmentName: '', equipmentModel: '', equipmentSpec: '', manufacturer: '', equipmentLocation: '', equipmentTypeId: '', networkId: '', pointId: '', longitude: null, latitude: null, maintainer: '', maintainerPhone: '', remark: '' })
+  dialogVisible.value = true
+  loadEquipmentTypes(); loadNetworkOptions()
+}
+function handleEdit(row) {
+  dialogTitle.value = '编辑'
+  Object.assign(formData, row)
   dialogVisible.value = true
+  loadEquipmentTypes(); loadNetworkOptions()
+  if (row.networkId) loadPointOptions(row.networkId)
 }
-function handleEdit(row) { dialogTitle.value = '编辑'; Object.assign(formData, row); dialogVisible.value = true }
 async function handleSave() {
+  const valid = await formRef.value.validate().catch(() => false)
+  if (!valid) return
   try {
-    if (formData.equipmentId) { await updateEquipment(formData); ElMessage.success('修改成功') }
-    else { await addEquipment(formData); ElMessage.success('新增成功') }
+    if (formData.equipmentId) {
+      await updateEquipment(formData)
+    } else {
+      const res = await addEquipment(formData)
+      formData.equipmentId = res.data
+    }
+    if (formData.pointId && formData.equipmentId) {
+      await saveEquipmentDeviceRel({ equipmentId: formData.equipmentId, pointId: formData.pointId }).catch(() => {})
+    }
+    ElMessage.success(formData.equipmentId ? '修改成功' : '新增成功')
     dialogVisible.value = false; loadData()
-  } catch (e) { console.error(e) }
+  } catch (e) {
+    const msg = e?.response?.data?.msg || e?.message || '操作失败'
+    ElMessage.error(msg)
+  }
 }
 function handleDelete(row) {
   ElMessageBox.confirm('确认删除该设备信息?', '提示', { type: 'warning' }).then(async () => {

+ 120 - 17
src/views/subSystem/basic/Manhole.vue

@@ -28,7 +28,12 @@
       <el-table :data="tableData" border stripe v-loading="loading">
         <el-table-column prop="manholeCode" label="窨井编码" min-width="120" />
         <el-table-column prop="manholeName" label="窨井名称" min-width="150" />
-        <el-table-column prop="networkId" label="关联管网ID" min-width="120" />
+        <el-table-column prop="networkId" label="关联管网" min-width="150">
+          <template #default="{ row }">
+            <span v-if="row.networkId">{{ getNetworkName(row.networkId) }}</span>
+            <el-tag v-else type="info" size="small">未关联</el-tag>
+          </template>
+        </el-table-column>
         <el-table-column prop="location" label="位置" min-width="180" />
         <el-table-column prop="manholeDepth" label="深度(米)" width="90" />
         <el-table-column prop="manholeShape" label="形状" width="80">
@@ -58,6 +63,7 @@
 
     <el-card v-else class="map-card">
       <div id="manholeMap" style="width: 100%; height: 600px"></div>
+      <div v-if="mapError" style="position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);color:#f56c6c;font-size:14px;">{{ mapError }}</div>
     </el-card>
 
     <el-dialog title="关联监测设备" v-model="deviceDialogVisible" width="950px" destroy-on-close>
@@ -100,7 +106,7 @@
     </el-dialog>
 
     <el-dialog :title="dialogTitle" v-model="dialogVisible" width="650px" destroy-on-close>
-      <el-form :model="formData" ref="formRef" label-width="100px">
+      <el-form :model="formData" :rules="rules" ref="formRef" label-width="100px">
         <el-form-item label="窨井编码" prop="manholeCode">
           <el-input v-model="formData.manholeCode" placeholder="请输入窨井编码" />
         </el-form-item>
@@ -108,15 +114,17 @@
           <el-input v-model="formData.manholeName" placeholder="请输入窨井名称" />
         </el-form-item>
         <el-form-item label="关联管网">
-          <el-input v-model="formData.networkId" placeholder="请输入管网设施ID" />
+          <el-select v-model="formData.networkId" placeholder="请选择管网" clearable style="width: 100%">
+            <el-option v-for="n in networkOptions" :key="n.networkId" :label="`${n.networkName}(${n.networkCode})`" :value="n.networkId" />
+          </el-select>
         </el-form-item>
-        <el-form-item label="位置描述">
+        <el-form-item label="位置描述" prop="location">
           <el-input v-model="formData.location" placeholder="请输入位置描述" />
         </el-form-item>
-        <el-form-item label="经度">
+        <el-form-item label="经度" prop="longitude">
           <el-input-number v-model="formData.longitude" :precision="10" :step="0.0001" style="width: 100%" />
         </el-form-item>
-        <el-form-item label="纬度">
+        <el-form-item label="纬度" prop="latitude">
           <el-input-number v-model="formData.latitude" :precision="10" :step="0.0001" style="width: 100%" />
         </el-form-item>
         <el-form-item label="深度(米)">
@@ -151,12 +159,12 @@
 </template>
 
 <script setup>
-import { ref, reactive, computed, onMounted, nextTick, watch } from 'vue'
+import { ref, reactive, computed, onMounted, nextTick, watch, onBeforeUnmount } from 'vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import {
   getManholePage, addManhole, updateManhole, deleteManhole,
   getManholeDevices, addManholeDeviceRel, deleteManholeDeviceRel,
-  getEquipmentList
+  getEquipmentList, getPipeNetworkOptions
 } from '@/api/pipeNetwork/basic'
 import { getDicts } from '@/api/system/dict/data'
 
@@ -176,9 +184,17 @@ const formData = reactive({
   location: '', longitude: null, latitude: null, manholeDepth: null,
   manholeShape: '', material: '', ownershipUnit: '', status: '0', remark: ''
 })
+const rules = {
+  manholeCode: [{ required: true, message: '请输入窨井编码', trigger: 'blur' }],
+  manholeName: [{ required: true, message: '请输入窨井名称', trigger: 'blur' }],
+  longitude: [{ required: true, message: '请输入经度', trigger: 'blur' }],
+  latitude: [{ required: true, message: '请输入纬度', trigger: 'blur' }],
+  location: [{ required: true, message: '请输入位置描述', trigger: 'blur' }]
+}
 
 const pipeNetworkStatusDict = ref([])
 const manholeShapeDict = ref([])
+const networkOptions = ref([])
 
 const deviceDialogVisible = ref(false)
 const currentManhole = reactive({ manholeId: '', manholeName: '' })
@@ -194,6 +210,10 @@ function getDictLabel(dict, value) {
   const item = dict.find(d => d.dictValue === value)
   return item ? item.dictLabel : value
 }
+function getNetworkName(networkId) {
+  const found = networkOptions.value.find(n => n.networkId === networkId)
+  return found ? found.networkName : networkId
+}
 
 async function loadDicts() {
   const [statusRes, shapeRes] = await Promise.all([
@@ -210,23 +230,37 @@ async function loadData() {
     const res = await getManholePage(pageNum.value, pageSize.value, searchForm)
     tableData.value = res.data.records || []
     total.value = res.data.total || 0
+    if (viewMode.value === 'map' && mapInitialized) { renderMarkers() }
   } catch (e) { console.error(e) } finally { loading.value = false }
 }
 
 function handleSearch() { pageNum.value = 1; loadData() }
 function handleReset() { Object.assign(searchForm, { manholeName: '', manholeCode: '', status: '' }); handleSearch() }
+async function loadNetworkOptions() {
+  if (networkOptions.value.length > 0) return
+  try {
+    const res = await getPipeNetworkOptions()
+    networkOptions.value = res.data || []
+  } catch (e) { console.error(e) }
+}
 function handleAdd() {
   dialogTitle.value = '新增'
   Object.assign(formData, { manholeId: '', manholeCode: '', manholeName: '', networkId: '', location: '', longitude: null, latitude: null, manholeDepth: null, manholeShape: '', material: '', ownershipUnit: '', status: '0', remark: '' })
   dialogVisible.value = true
+  loadNetworkOptions()
 }
-function handleEdit(row) { dialogTitle.value = '编辑'; Object.assign(formData, row); dialogVisible.value = true }
+function handleEdit(row) { dialogTitle.value = '编辑'; Object.assign(formData, row); dialogVisible.value = true; loadNetworkOptions() }
 async function handleSubmit() {
+  const valid = await formRef.value.validate().catch(() => false)
+  if (!valid) return
   try {
     if (formData.manholeId) { await updateManhole(formData); ElMessage.success('修改成功') }
     else { await addManhole(formData); ElMessage.success('新增成功') }
     dialogVisible.value = false; loadData()
-  } catch (e) { console.error(e) }
+  } catch (e) {
+    const msg = e?.response?.data?.msg || e?.message || '操作失败'
+    ElMessage.error(msg)
+  }
 }
 function handleDelete(row) {
   ElMessageBox.confirm('确认删除该窨井信息?', '提示', { type: 'warning' }).then(async () => {
@@ -302,22 +336,91 @@ function handleUnbind(dev) {
   }).catch(() => {})
 }
 
+let mapInstance = null
+let mapInitialized = false
+const mapError = ref('')
+
+function waitForBMapGL() {
+  return new Promise((resolve, reject) => {
+    if (window.BMapGL) { resolve(window.BMapGL); return }
+    let attempts = 0
+    const timer = setInterval(() => {
+      attempts++
+      if (window.BMapGL) { clearInterval(timer); resolve(window.BMapGL); return }
+      if (attempts >= 80) { clearInterval(timer); reject(new Error('百度地图加载超时')) }
+    }, 200)
+  })
+}
+
 async function initMap() {
   await nextTick()
   const el = document.getElementById('manholeMap')
-  if (!el) return
- /* try {
-    const L = await import('leaflet')
+  if (!el || mapInitialized) return
+
+  try {
+    const BMapGL = await waitForBMapGL()
+    mapInstance = new BMapGL.Map(el, { enableMapClick: true })
+    const center = tableData.value.length > 0 && tableData.value[0].longitude
+      ? [Number(tableData.value[0].longitude), Number(tableData.value[0].latitude)]
+      : [110.3965, 28.4638]
+    mapInstance.centerAndZoom(new BMapGL.Point(center[0], center[1]), 15)
+    mapInstance.enableScrollWheelZoom(true)
+    mapInitialized = true
+    renderMarkers()
   } catch (e) {
-    el.innerHTML = '<div style="display:flex;align-items:center;justify-content:center;height:100%;color:#999;">地图加载需要Leaflet依赖,请安装: npm install leaflet</div>'
-  }*/
+    console.error(e)
+    mapError.value = '百度地图加载失败,请刷新页面重试'
+  }
 }
-watch(viewMode, (v) => { if (v === 'map') initMap() })
 
-onMounted(async () => { await loadDicts(); loadData() })
+function renderMarkers() {
+  if (!mapInstance || typeof BMapGL === 'undefined') return
+  try { mapInstance.clearOverlays() } catch (_) {}
+
+  const data = tableData.value.filter(m => m.longitude && m.latitude)
+  if (data.length === 0) return
+
+  data.forEach(m => {
+    const lng = Number(m.longitude)
+    const lat = Number(m.latitude)
+    const pt = new BMapGL.Point(lng, lat)
+    const marker = new BMapGL.Marker(pt)
+    mapInstance.addOverlay(marker)
+
+    const shapeLabel = getDictLabel(manholeShapeDict.value, m.manholeShape)
+    const statusLabel = getDictLabel(pipeNetworkStatusDict.value, m.status)
+    const html = `<div style="font-size:13px;line-height:1.8;max-width:260px;">
+      <b>${m.manholeName}</b><br/>
+      编码:${m.manholeCode}<br/>
+      位置:${m.location || '-'}<br/>
+      深度:${m.manholeDepth != null ? m.manholeDepth + 'm' : '-'}<br/>
+      形状:${shapeLabel}<br/>
+      材质:${m.material || '-'}<br/>
+      状态:${statusLabel}
+    </div>`
+
+    marker.addEventListener('click', () => {
+      const infoWindow = new BMapGL.InfoWindow(html, { width: 240 })
+      mapInstance.openInfoWindow(infoWindow, pt)
+    })
+  })
+}
+
+watch(viewMode, (v) => { if (v === 'map') { initMap() } else { mapInitialized = false } })
+
+onMounted(async () => { await loadDicts(); loadData(); loadNetworkOptions() })
+
+onBeforeUnmount(() => {
+  if (mapInstance) {
+    try { mapInstance.destroy() } catch (_) {}
+    mapInstance = null
+    mapInitialized = false
+  }
+})
 </script>
 
 <style scoped>
 .manhole-container { padding: 16px; }
 .search-card { margin-bottom: 16px; }
+.map-card { position: relative; }
 </style>

+ 15 - 4
src/views/subSystem/basic/PipeNetwork.vue

@@ -123,9 +123,10 @@
 
 <script setup>
 import { ref, reactive, onMounted } from 'vue'
-import { ElMessage, ElMessageBox } from 'element-plus'
-import { getPipeNetworkPage, getPipeNetworkById, addPipeNetwork, updatePipeNetwork, deletePipeNetwork } from '@/api/pipeNetwork/basic'
+import { ElMessage, ElMessageBox, ElLoading } from 'element-plus'
+import { getPipeNetworkPage, getPipeNetworkById, addPipeNetwork, updatePipeNetwork, deletePipeNetwork, exportPipeNetwork } from '@/api/pipeNetwork/basic'
 import { getDicts } from '@/api/system/dict/data'
+import { saveAs } from 'file-saver'
 
 const loading = ref(false)
 const tableData = ref([])
@@ -232,8 +233,18 @@ function handleDelete(row) {
   }).catch(() => {})
 }
 
-function handleExport() {
-  ElMessage.info('导出功能开发中')
+async function handleExport() {
+  const loading = ElLoading.service({ text: '正在导出数据,请稍候', background: 'rgba(0, 0, 0, 0.7)' })
+  try {
+    const blob = await exportPipeNetwork({ networkName: searchForm.networkName, status: searchForm.status })
+    saveAs(new Blob([blob], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }), `燃气管网数据_${new Date().toISOString().slice(0, 10)}.xlsx`)
+    ElMessage.success('导出成功')
+  } catch (e) {
+    console.error(e)
+    ElMessage.error('导出失败,请稍后重试')
+  } finally {
+    loading.close()
+  }
 }
 
 onMounted(async () => { await loadDicts(); loadData() })

+ 12 - 4
src/views/subSystem/basic/PointManage.vue

@@ -35,7 +35,7 @@
         <el-table-column label="操作" width="200" fixed="right">
           <template #default="{ row }">
             <el-button type="primary" size="small" @click="handleEdit(row)">编辑</el-button>
-            <el-button type="info" size="small" @click="handleRelDevice(row)">关联设备</el-button>
+<!--            <el-button type="info" size="small" @click="handleRelDevice(row)">关联设备</el-button>-->
             <el-button type="danger" size="small" @click="handleDelete(row)">删除</el-button>
           </template>
         </el-table-column>
@@ -100,7 +100,9 @@
           </el-select>
         </el-form-item>
         <el-form-item label="所属管网">
-          <el-input v-model="formData.networkId" placeholder="请输入管网ID" />
+          <el-select v-model="formData.networkId" placeholder="请选择管网" clearable style="width: 100%">
+            <el-option v-for="n in networkOptions" :key="n.networkId" :label="`${n.networkName}(${n.networkCode})`" :value="n.networkId" />
+          </el-select>
         </el-form-item>
         <el-form-item label="位置描述">
           <el-input v-model="formData.location" />
@@ -128,7 +130,7 @@
 <script setup>
 import { ref, reactive, computed, onMounted } from 'vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
-import { getPointPage, addPoint, updatePoint, deletePoint, getEquipmentByPoint, savePointRel, deletePointRel, getEquipmentList } from '@/api/pipeNetwork/basic'
+import { getPointPage, addPoint, updatePoint, deletePoint, getEquipmentByPoint, savePointRel, deletePointRel, getEquipmentList, getPipeNetworkOptions } from '@/api/pipeNetwork/basic'
 import { getDicts } from '@/api/system/dict/data'
 
 const loading = ref(false)
@@ -147,6 +149,7 @@ const formData = reactive({
 
 const pointTypeDict = ref([])
 const pointStatusDict = ref([])
+const networkOptions = ref([])
 
 const deviceDialogVisible = ref(false)
 const currentPoint = reactive({ pointId: '', pointName: '' })
@@ -183,12 +186,17 @@ async function loadData() {
 }
 function handleSearch() { pageNum.value = 1; loadData() }
 function handleReset() { Object.assign(searchForm, { pointName: '', pointType: '' }); handleSearch() }
+async function loadNetworkOptions() {
+  if (networkOptions.value.length > 0) return
+  try { const res = await getPipeNetworkOptions(); networkOptions.value = res.data || [] } catch (e) { console.error(e) }
+}
 function handleAdd() {
   dialogTitle.value = '新增测点'
   Object.assign(formData, { pointId: '', pointCode: '', pointName: '', pointType: '', networkId: '', location: '', longitude: null, latitude: null, status: 'ACTIVE' })
   dialogVisible.value = true
+  loadNetworkOptions()
 }
-function handleEdit(row) { dialogTitle.value = '编辑'; Object.assign(formData, row); dialogVisible.value = true }
+function handleEdit(row) { dialogTitle.value = '编辑'; Object.assign(formData, row); dialogVisible.value = true; loadNetworkOptions() }
 async function handleSave() {
   try {
     if (formData.pointId) { await updatePoint(formData); ElMessage.success('修改成功') }

+ 21 - 21
src/views/subSystem/hidden/HazardMap.vue

@@ -68,7 +68,7 @@
             <span class="divider">|</span>
             <span :style="{ color: levelColors[h.hazardLevel], fontWeight: '500' }">{{ getDictLabel(hazardLevelDict, h.hazardLevel) }}隐患</span>
           </div>
-          <div class="item-addr">{{ h.address }}</div>
+          <div class="item-addr">{{ h.remark || h.hazardDesc }}</div>
           <div class="item-time">{{ h.reportTime }}</div>
         </div>
         <el-empty v-if="filteredHazards.length === 0" description="暂无匹配隐患" :image-size="60" />
@@ -100,7 +100,7 @@
           <el-descriptions-item label="上报单位">{{ currentHazard.reportUnit }}</el-descriptions-item>
           <el-descriptions-item label="上报时间">{{ currentHazard.reportTime }}</el-descriptions-item>
           <el-descriptions-item label="隐患描述">{{ currentHazard.hazardDesc }}</el-descriptions-item>
-          <el-descriptions-item label="详细地址">{{ currentHazard.address }}</el-descriptions-item>
+          <el-descriptions-item label="备注">{{ currentHazard.remark || '-' }}</el-descriptions-item>
           <el-descriptions-item label="整改方案">{{ currentHazard.rectifyPlan || '暂无' }}</el-descriptions-item>
           <el-descriptions-item label="整改反馈">{{ currentHazard.rectifyFeedback || '暂无' }}</el-descriptions-item>
         </el-descriptions>
@@ -112,12 +112,13 @@
 <script setup>
 import { ref, computed, onMounted, onBeforeUnmount, nextTick } from 'vue'
 import { getDicts } from '@/api/system/dict/data'
-import { convertCoord } from '@/utils/coordTransform'
+import { getHazardMapPoints } from '@/api/pipeNetwork/hazard'
 
 const detailVisible = ref(false)
 const currentHazard = ref(null)
 const activeHazardId = ref('')
 const mapError = ref('')
+const loading = ref(false)
 let mapInstance = null
 let activeInfoWindow = null
 let mapMarkers = []
@@ -156,18 +157,11 @@ function switchStatusTab(val) {
   applyFilter()
 }
 
-// ==================== 沅陵县静态隐患数据 ====================
-const mockHazards = [
-  { hazardId: 'HZ001', hazardNo: 'YL-2026-001', hazardType: '0', hazardLevel: '0', hazardStatus: '1', reportUnit: '片区管理站', reportTime: '2026-06-12 09:15', hazardDesc: '燃气1管道被第三方道路施工挖掘机挖伤,管体出现明显划痕及凹坑', address: '辰州路与建设路交叉口东侧', lng: 110.388, lat: 28.445, rectifyPlan: '立即停气更换受损管段', rectifyFeedback: '' },
-  { hazardId: 'HZ002', hazardNo: 'YL-2026-002', hazardType: '1', hazardLevel: '1', hazardStatus: '1', reportUnit: '街道办', reportTime: '2026-06-11 14:30', hazardDesc: '燃气1管道外防腐层大面积脱落,管体局部锈蚀', address: '古城路中段居民区附近', lng: 110.396, lat: 28.442, rectifyPlan: '重新做3PE防腐层', rectifyFeedback: '材料已采购' },
-  { hazardId: 'HZ005', hazardNo: 'YL-2026-005', hazardType: '1', hazardLevel: '1', hazardStatus: '2', reportUnit: '片区管理站', reportTime: '2026-05-08 16:40', hazardDesc: '燃气2管道正上方有违规搭建,且使用明火经营', address: '迎宾路农贸市场旁', lng: 110.365, lat: 28.448, rectifyPlan: '联合执法部门取缔占压', rectifyFeedback: '' },
-  { hazardId: 'HZ006', hazardNo: 'YL-2026-006', hazardType: '0', hazardLevel: '2', hazardStatus: '3', reportUnit: '住建局', reportTime: '2026-05-05 09:00', hazardDesc: '电力电缆与燃气管道同沟敷设,安全间距不足', address: '学府路电力井附近', lng: 110.394, lat: 28.455, rectifyPlan: '增设绝缘隔离板', rectifyFeedback: '' },
-  { hazardId: 'HZ007', hazardNo: 'YL-2026-007', hazardType: '2', hazardLevel: '2', hazardStatus: '1', reportUnit: '街道办', reportTime: '2026-04-22 11:00', hazardDesc: '管道标识桩缺失,标识带破损严重', address: '滨江路绿化带内', lng: 110.409, lat: 28.438, rectifyPlan: '补设管道标识桩', rectifyFeedback: '' },
-  { hazardId: 'HZ008', hazardNo: 'YL-2026-008', hazardType: '4', hazardLevel: '3', hazardStatus: '3', reportUnit: '工程部', reportTime: '2026-04-15 14:00', hazardDesc: '阀门井盖破损,井内积水严重', address: '城东大道阀门井', lng: 110.403, lat: 28.458, rectifyPlan: '更换阀门井盖', rectifyFeedback: '已修复' },
-]
+// ==================== 隐患数据(从接口加载) ====================
+const allHazards = ref([])
 
 const filteredHazards = computed(() => {
-  let data = mockHazards
+  let data = allHazards.value
   if (filterType.value) data = data.filter(h => h.hazardType === filterType.value)
   if (filterLevel.value) data = data.filter(h => h.hazardLevel === filterLevel.value)
   if (listStatusTab.value) data = data.filter(h => h.hazardStatus === listStatusTab.value)
@@ -179,12 +173,12 @@ const levelLegend = computed(() =>
     value: d.dictValue,
     label: d.dictLabel,
     color: levelColors[d.dictValue] || '#999',
-    count: mockHazards.filter(h => h.hazardLevel === d.dictValue).length
+    count: allHazards.value.filter(h => h.hazardLevel === d.dictValue).length
   }))
 )
 
 // ==================== 百度地图 ====================
-const MAP_CENTER = { lng: 110.386, lat: 28.445 }
+const MAP_CENTER = { lng: 110.3965, lat: 28.4638 }
 const MAP_DEFAULT_ZOOM = 14
 
 function waitForBMapGL() {
@@ -217,8 +211,7 @@ function initMap() {
   }
 
   try {
-    const [mcLng, mcLat] = convertCoord(MAP_CENTER.lng, MAP_CENTER.lat)
-    mapInstance.centerAndZoom(new BMapGL.Point(mcLng, mcLat), MAP_DEFAULT_ZOOM)
+    mapInstance.centerAndZoom(new BMapGL.Point(MAP_CENTER.lng, MAP_CENTER.lat), MAP_DEFAULT_ZOOM)
     mapInstance.enableScrollWheelZoom(true)
     mapInstance.setMinZoom(12)
     mapInstance.setMaxZoom(18)
@@ -241,8 +234,7 @@ function renderMapMarkers() {
   mapMarkers = []
 
   filteredHazards.value.forEach(h => {
-    const [bLng, bLat] = convertCoord(h.lng, h.lat)
-    const pt = new BMapGL.Point(bLng, bLat)
+    const pt = new BMapGL.Point(h.longitude, h.latitude)
     const color = levelColors[h.hazardLevel] || '#999'
     const isActive = activeHazardId.value === h.hazardId
     const size = isActive ? 16 : 12
@@ -283,8 +275,7 @@ function openHazardInfo(h) {
   activeHazardId.value = h.hazardId
   detailVisible.value = true
 
-  const [bLng, bLat] = convertCoord(h.lng, h.lat)
-  const pt = new BMapGL.Point(bLng, bLat)
+  const pt = new BMapGL.Point(h.longitude, h.latitude)
   const color = levelColors[h.hazardLevel] || '#999'
   const levelLabel = getDictLabel(hazardLevelDict.value, h.hazardLevel)
   const typeLabel = getDictLabel(hazardTypeDict.value, h.hazardType)
@@ -367,8 +358,17 @@ async function loadDicts() {
   }
 }
 
+async function loadHazards() {
+  loading.value = true
+  try {
+    const res = await getHazardMapPoints()
+    allHazards.value = res.data || []
+  } catch (e) { console.error(e) } finally { loading.value = false }
+}
+
 onMounted(async () => {
   await loadDicts()
+  await loadHazards()
   await nextTick()
   waitForBMapGL()
 })