yjsdgl.vue 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885
  1. <template>
  2. <div class="app-container">
  3. <!-- GIS 地图展示区 -->
  4. <div class="map-section" style="margin-bottom: 16px;">
  5. <div class="map-tabs">
  6. <div class="map-tab active">易积水点分布</div>
  7. </div>
  8. <div id="yjsdglMainMap" class="map-container" style="height: 350px;"></div>
  9. </div>
  10. <!-- 搜索区域 -->
  11. <el-form :model="queryParams" ref="queryRef" :inline="true" class="search-form">
  12. <el-form-item label="点位名称">
  13. <el-input v-model="queryParams.name" placeholder="请输入" clearable style="width: 200px" />
  14. </el-form-item>
  15. <el-form-item label="地点">
  16. <el-input v-model="queryParams.area" placeholder="请输入" clearable style="width: 200px" />
  17. </el-form-item>
  18. <el-form-item>
  19. <el-button type="primary" @click="handleQuery">搜索</el-button>
  20. </el-form-item>
  21. </el-form>
  22. <!-- 操作按钮区域 -->
  23. <div class="toolbar">
  24. <el-button type="primary" @click="handleAdd">新增</el-button>
  25. <el-button type="danger" :disabled="selectedIds.length === 0" @click="handleBatchDelete">删除</el-button>
  26. </div>
  27. <!-- 数据表格 -->
  28. <el-table v-loading="loading" :data="filteredList" border style="width: 100%"
  29. @selection-change="handleSelectionChange">
  30. <el-table-column type="selection" width="55" align="center" />
  31. <el-table-column label="监测点名称" prop="name" min-width="160" />
  32. <el-table-column label="地点" prop="area" min-width="100" />
  33. <el-table-column label="数据时间" prop="time" min-width="120" />
  34. <el-table-column label="易积水点数量" prop="waterloggingCount" min-width="100" />
  35. <el-table-column label="积水量" prop="waterVolume" min-width="100" />
  36. <el-table-column label="积水程度" min-width="120">
  37. <template #default="{ row }">D:\work1\city-life-line\src\views\subSystem\drainage\DrainageWorkOrder.vue
  38. <el-tag :type="getWaterloggingLevelType(row.waterloggingLevel)" size="small">
  39. {{ getWaterloggingLevelLabel(row.waterloggingLevel) }}
  40. </el-tag>
  41. </template>
  42. </el-table-column>
  43. <el-table-column label="关联设备" min-width="160">
  44. <template #default="{ row }">
  45. {{ equipmentNameMap[row.id] || '' }}
  46. </template>
  47. </el-table-column>
  48. <el-table-column label="操作" width="240" align="center">
  49. <template #default="{ row }">
  50. <el-button link type="primary" @click="handleView(row)">查看</el-button>
  51. <el-button link type="primary" @click="handleEdit(row)">修改</el-button>
  52. <el-button link type="primary" @click="handleBind(row)">关联设备</el-button>
  53. <el-button link type="danger" @click="handleDelete(row)">删除</el-button>
  54. </template>
  55. </el-table-column>
  56. </el-table>
  57. <!-- 分页 -->
  58. <el-pagination v-model:current-page="pageNum" v-model:page-size="pageSize" :total="total"
  59. :page-sizes="[10, 20, 50, 100]" layout="total, sizes, prev, pager, next, jumper"
  60. style="margin-top: 16px; justify-content: flex-end;" @size-change="loadData" @current-change="loadData" />
  61. <!-- 查看详情对话框 -->
  62. <el-dialog v-model="dialogVisible" title="详情" width="700px" :before-close="handleClose" @opened="onDialogOpened">
  63. <div class="detail-info">
  64. <el-row :gutter="20">
  65. <el-col :span="12">
  66. <div class="info-item">
  67. <span class="info-label">监测点名称:</span>
  68. <span class="info-value">{{ currentRow.name }}</span>
  69. </div>
  70. </el-col>
  71. <el-col :span="12">
  72. <div class="info-item">
  73. <span class="info-label">地点:</span>
  74. <span class="info-value">{{ currentRow.area }}</span>
  75. </div>
  76. </el-col>
  77. </el-row>
  78. <el-row :gutter="20">
  79. <el-col :span="12">
  80. <div class="info-item">
  81. <span class="info-label">数据时间:</span>
  82. <span class="info-value">{{ currentRow.time }}</span>
  83. </div>
  84. </el-col>
  85. <el-col :span="12">
  86. <div class="info-item">
  87. <span class="info-label">易积水点数量:</span>
  88. <span class="info-value">{{ currentRow.waterloggingPoints.length }}</span>
  89. </div>
  90. </el-col>
  91. </el-row>
  92. <el-row :gutter="20">
  93. <el-col :span="12">
  94. <div class="info-item">
  95. <span class="info-label">积水量:</span>
  96. <span class="info-value">{{ currentRow.waterVolume }}</span>
  97. </div>
  98. </el-col>
  99. <el-col :span="12">
  100. <div class="info-item">
  101. <span class="info-label">积水程度:</span>
  102. <span class="info-value">
  103. <el-tag :type="getWaterloggingLevelType(currentRow.waterloggingLevel)" size="small">
  104. {{ getWaterloggingLevelLabel(currentRow.waterloggingLevel) }}
  105. </el-tag>
  106. </span>
  107. </div>
  108. </el-col>
  109. </el-row>
  110. </div>
  111. <!-- 关联设备 -->
  112. <div v-if="viewEquipmentNames.length" class="detail-info" style="margin-top: 12px;">
  113. <el-row :gutter="20">
  114. <el-col :span="24">
  115. <div class="info-item">
  116. <span class="info-label">关联设备:</span>
  117. <span class="info-value">{{ viewEquipmentNames.join('、') }}</span>
  118. </div>
  119. </el-col>
  120. </el-row>
  121. </div>
  122. <!-- 地图区域 -->
  123. <div class="map-section">
  124. <div id="yjsdglViewMap" class="map-container"></div>
  125. </div>
  126. </el-dialog>
  127. <!-- 新增/修改对话框 -->
  128. <el-dialog v-model="editDialogVisible" :title="editDialogTitle" width="800px" :before-close="handleEditClose"
  129. @opened="onEditDialogOpened">
  130. <el-form ref="editFormRef" :model="editForm" :rules="editRules" label-width="100px">
  131. <el-row :gutter="20">
  132. <el-col :span="12">
  133. <el-form-item label="监测点名称" prop="name">
  134. <el-input v-model="editForm.name" placeholder="请输入监测点名称" />
  135. </el-form-item>
  136. </el-col>
  137. <el-col :span="12">
  138. <el-form-item label="地点" prop="area">
  139. <el-input v-model="editForm.area" placeholder="请输入地点" />
  140. </el-form-item>
  141. </el-col>
  142. </el-row>
  143. <el-row :gutter="20">
  144. <el-col :span="12">
  145. <el-form-item label="数据时间" prop="time">
  146. <el-date-picker v-model="editForm.time" type="date" placeholder="请选择日期" value-format="YYYY-MM-DD"
  147. style="width: 100%" />
  148. </el-form-item>
  149. </el-col>
  150. <el-col :span="12">
  151. <el-form-item label="易积水点数量">
  152. <el-input :model-value="editWaterloggingPoints.length" disabled />
  153. </el-form-item>
  154. </el-col>
  155. </el-row>
  156. <el-row :gutter="20">
  157. <el-col :span="12">
  158. <el-form-item label="积水量" prop="waterVolume">
  159. <el-input v-model="editForm.waterVolume" type="number" placeholder="请输入积水量" />
  160. </el-form-item>
  161. </el-col>
  162. <el-col :span="12">
  163. <el-form-item label="积水程度" prop="waterloggingLevel">
  164. <el-select v-model="editForm.waterloggingLevel" placeholder="请选择积水程度" style="width: 100%">
  165. <el-option label="未积水" :value="0" />
  166. <el-option label="轻度积水" :value="1" />
  167. <el-option label="中度积水" :value="2" />
  168. <el-option label="严重积水" :value="3" />
  169. </el-select>
  170. </el-form-item>
  171. </el-col>
  172. </el-row>
  173. </el-form>
  174. <!-- 地图标点区域 -->
  175. <div class="map-section" style="margin-top: 16px;">
  176. <div class="map-tabs">
  177. <div class="map-tab active">易积水点</div>
  178. <div class="map-tab-action" @click="clearEditMarkers">清除所有标记</div>
  179. </div>
  180. <div id="yjsdglEditMap" class="map-container" style="height: 350px;"></div>
  181. <div class="marker-count">
  182. 已标记 {{ editWaterloggingPoints.length }} 个易积水点
  183. </div>
  184. </div>
  185. <template #footer>
  186. <el-button @click="handleEditClose">取 消</el-button>
  187. <el-button type="primary" @click="handleEditSubmit">确 定</el-button>
  188. </template>
  189. </el-dialog>
  190. <!-- 关联设备弹框 -->
  191. <el-dialog v-model="bindDialogVisible" title="关联设备" width="700px" @close="closeBindDialog">
  192. <el-form :inline="true" :model="bindQueryParams" style="margin-bottom: 12px;">
  193. <el-form-item label="设备名称">
  194. <el-input v-model="bindQueryParams.equipmentName" placeholder="请输入设备名称" clearable />
  195. </el-form-item>
  196. <el-form-item>
  197. <el-button type="primary" @click="handleBindQuery">搜索</el-button>
  198. </el-form-item>
  199. </el-form>
  200. <el-table v-loading="bindLoading" :data="bindTableData" border row-key="equipmentId"
  201. @selection-change="handleBindSelectionChange" ref="bindTableRef">
  202. <el-table-column type="selection" width="55" align="center" :reserve-selection="true" />
  203. <el-table-column label="设备名称" prop="equipmentName" min-width="120" />
  204. <el-table-column label="设备编号" prop="equipmentCode" min-width="120" />
  205. <el-table-column label="设备类型" prop="equipmentTypeName" min-width="100" />
  206. <el-table-column label="安装位置" prop="installLocation" min-width="120" />
  207. </el-table>
  208. <el-pagination v-model:current-page="bindPageNum" v-model:page-size="bindPageSize" :total="bindTotal"
  209. :page-sizes="[10, 20, 50]" layout="total, sizes, prev, pager, next, jumper"
  210. style="margin-top: 12px; justify-content: flex-end;" @size-change="loadBindData" @current-change="loadBindData" />
  211. <template #footer>
  212. <el-button @click="closeBindDialog">取 消</el-button>
  213. <el-button type="primary" @click="submitBind">确 定</el-button>
  214. </template>
  215. </el-dialog>
  216. </div>
  217. </template>
  218. <script setup name="Yjsdgl">
  219. import { ref, computed, nextTick, onMounted } from 'vue'
  220. import { ElMessage, ElMessageBox } from 'element-plus'
  221. import locationIcon from '@/assets/images/location.png'
  222. import {
  223. getWaterloggingPage,
  224. addWaterlogging,
  225. updateWaterlogging,
  226. deleteWaterlogging,
  227. getEquipmentPage,
  228. getEquipmentById,
  229. bindWaterloggingEquipment
  230. } from '@/api/drainage'
  231. // 地图标记创建(用Label+img代替Marker)
  232. function createMapMarker(map, bPoint, name) {
  233. const html = `<div style="text-align:center;cursor:pointer;">
  234. <div style="color:#fff;background:#409eff;border-radius:4px;padding:2px 6px;font-size:11px;white-space:nowrap;display:inline-block;margin-bottom:2px;">${name || ''}</div>
  235. <div><img src="${locationIcon}" style="width:28px;height:28px;display:block;margin:0 auto;"/></div>
  236. </div>`
  237. const label = new BMapGL.Label(html, {
  238. position: bPoint,
  239. offset: new BMapGL.Size(-30, -50)
  240. })
  241. label.setStyle({
  242. border: 'none',
  243. background: 'transparent',
  244. padding: '0'
  245. })
  246. map.addOverlay(label)
  247. return label
  248. }
  249. // 积水程度选项
  250. const waterloggingLevelOptions = [
  251. { value: 0, label: '未积水', type: 'success' },
  252. { value: 1, label: '轻度积水', type: 'info' },
  253. { value: 2, label: '中度积水', type: 'warning' },
  254. { value: 3, label: '严重积水', type: 'danger' }
  255. ]
  256. function getWaterloggingLevelLabel(level) {
  257. const option = waterloggingLevelOptions.find(item => item.value === level)
  258. return option ? option.label : '未知'
  259. }
  260. function getWaterloggingLevelType(level) {
  261. const option = waterloggingLevelOptions.find(item => item.value === level)
  262. return option ? option.type : 'info'
  263. }
  264. // 安全解析 waterloggingPoints(后端返回 JSON 字符串,需转为数组使用)
  265. function parseWaterloggingPoints(raw) {
  266. if (!raw) return []
  267. if (Array.isArray(raw)) return raw
  268. if (typeof raw === 'string') {
  269. try {
  270. const parsed = JSON.parse(raw)
  271. return Array.isArray(parsed) ? parsed : []
  272. } catch (e) {
  273. console.warn('waterloggingPoints JSON 解析失败:', raw, e)
  274. return []
  275. }
  276. }
  277. return []
  278. }
  279. // ==================== 搜索参数 ====================
  280. const queryParams = ref({
  281. name: '',
  282. area: ''
  283. })
  284. // ==================== 表格数据 ====================
  285. const tableData = ref([])
  286. const total = ref(0)
  287. const pageNum = ref(1)
  288. const pageSize = ref(10)
  289. const loading = ref(false)
  290. // 加载分页数据
  291. async function loadData() {
  292. loading.value = true
  293. try {
  294. const res = await getWaterloggingPage(pageNum.value, pageSize.value, queryParams.value)
  295. // 兼容直接返回Page对象(无code包装)和AjaxResult包装两种格式
  296. const pageData = res.code !== undefined ? res.data : res
  297. tableData.value = pageData.records || []
  298. total.value = pageData.total || 0
  299. renderMainMapMarkers()
  300. resolveEquipmentNames()
  301. } catch (e) {
  302. console.error('加载易积水点数据失败', e)
  303. } finally {
  304. loading.value = false
  305. }
  306. }
  307. // 关联设备名称映射
  308. const equipmentNameMap = ref({})
  309. async function resolveEquipmentNames() {
  310. const map = {}
  311. for (const row of tableData.value) {
  312. if (row.equipmentId) {
  313. const ids = row.equipmentId.split(',')
  314. const names = []
  315. for (const id of ids) {
  316. try {
  317. const res = await getEquipmentById(id.trim())
  318. const data = res.code !== undefined ? res.data : res
  319. const info = data && data.equipmentInfo ? data.equipmentInfo : data
  320. if (info && info.equipmentName) names.push(info.equipmentName)
  321. } catch (e) { /* skip */ }
  322. }
  323. map[row.id] = names.join('、')
  324. }
  325. }
  326. equipmentNameMap.value = map
  327. }
  328. onMounted(() => {
  329. loadData()
  330. setTimeout(() => { initMainMap() }, 500)
  331. })
  332. // ==================== 顶部主地图 ====================
  333. let mainMapInstance = null
  334. function initMainMap() {
  335. const container = document.getElementById('yjsdglMainMap')
  336. if (!container) return
  337. try {
  338. mainMapInstance = new BMapGL.Map('yjsdglMainMap')
  339. const centerPoint = new BMapGL.Point(110.393, 28.452)
  340. mainMapInstance.centerAndZoom(centerPoint, 12)
  341. mainMapInstance.enableScrollWheelZoom(true)
  342. renderMainMapMarkers()
  343. } catch (e) {
  344. console.error('百度地图初始化失败:', e)
  345. }
  346. }
  347. function renderMainMapMarkers() {
  348. if (!mainMapInstance) return
  349. mainMapInstance.clearOverlays()
  350. const allPoints = []
  351. tableData.value.forEach(row => {
  352. const points = parseWaterloggingPoints(row.waterloggingPoints)
  353. points.forEach(p => {
  354. if (p.lng && p.lat) {
  355. const bPoint = new BMapGL.Point(p.lng, p.lat)
  356. createMapMarker(mainMapInstance, bPoint, p.name || row.name)
  357. allPoints.push(bPoint)
  358. }
  359. })
  360. })
  361. if (allPoints.length > 0) {
  362. mainMapInstance.setViewport(allPoints)
  363. }
  364. }
  365. // 多选
  366. const selectedIds = ref([])
  367. function handleSelectionChange(selection) {
  368. selectedIds.value = selection.map(item => item.id)
  369. }
  370. // 表格数据直接用 tableData(已是分页数据)
  371. const filteredList = computed(() => tableData.value)
  372. // 搜索
  373. function handleQuery() {
  374. pageNum.value = 1
  375. loadData()
  376. }
  377. // ==================== 查看详情对话框 ====================
  378. const dialogVisible = ref(false)
  379. const currentRow = ref({})
  380. const viewEquipmentNames = ref([])
  381. let viewMapInstance = null
  382. async function handleView(row) {
  383. currentRow.value = {
  384. ...row,
  385. // 将 JSON 字符串解析为数组,供模板和地图渲染使用
  386. waterloggingPoints: parseWaterloggingPoints(row.waterloggingPoints)
  387. }
  388. dialogVisible.value = true
  389. viewEquipmentNames.value = []
  390. if (row.equipmentId) {
  391. const ids = row.equipmentId.split(',')
  392. const names = []
  393. for (const id of ids) {
  394. try {
  395. const res = await getEquipmentById(id.trim())
  396. const data = res.code !== undefined ? res.data : res
  397. const info = data && data.equipmentInfo ? data.equipmentInfo : data
  398. if (info && info.equipmentName) names.push(info.equipmentName)
  399. } catch (e) { /* skip */ }
  400. }
  401. viewEquipmentNames.value = names
  402. }
  403. }
  404. function handleClose() {
  405. dialogVisible.value = false
  406. if (viewMapInstance) {
  407. viewMapInstance = null
  408. }
  409. }
  410. function onDialogOpened() {
  411. setTimeout(() => {
  412. initViewMap()
  413. }, 300)
  414. }
  415. function initViewMap() {
  416. const container = document.getElementById('yjsdglViewMap')
  417. if (!container) return
  418. try {
  419. viewMapInstance = new BMapGL.Map('yjsdglViewMap')
  420. const centerPoint = new BMapGL.Point(110.393, 28.452)
  421. viewMapInstance.centerAndZoom(centerPoint, 15)
  422. viewMapInstance.enableScrollWheelZoom(true)
  423. renderViewMarkers()
  424. } catch (e) {
  425. console.error('百度地图初始化失败:', e)
  426. }
  427. }
  428. function renderViewMarkers() {
  429. if (!viewMapInstance) return
  430. viewMapInstance.clearOverlays()
  431. const points = currentRow.value.waterloggingPoints || []
  432. points.forEach(point => {
  433. const bPoint = new BMapGL.Point(point.lng, point.lat)
  434. createMapMarker(viewMapInstance, bPoint, point.name)
  435. })
  436. if (points.length > 0) {
  437. const viewPoints = points.map(p => new BMapGL.Point(p.lng, p.lat))
  438. viewMapInstance.setViewport(viewPoints)
  439. }
  440. }
  441. // ==================== 新增/修改对话框 ====================
  442. const editDialogVisible = ref(false)
  443. const editDialogTitle = ref('新增监测点')
  444. const editFormRef = ref(null)
  445. const isEditMode = ref(false)
  446. const editRowId = ref(null)
  447. const editForm = ref({
  448. name: '',
  449. area: '',
  450. time: '',
  451. waterVolume: null,
  452. waterloggingLevel: 0
  453. })
  454. const editRules = {
  455. name: [{ required: true, message: '请输入监测点名称', trigger: 'blur' }],
  456. area: [{ required: true, message: '请输入地点', trigger: 'blur' }],
  457. time: [{ required: true, message: '请选择数据时间', trigger: 'change' }]
  458. }
  459. // 编辑弹窗地图相关
  460. let editMapInstance = null
  461. let editWaterloggingPoints = ref([])
  462. let editWaterloggingMarkerCount = ref(0)
  463. function handleAdd() {
  464. isEditMode.value = false
  465. editDialogTitle.value = '新增监测点'
  466. editRowId.value = null
  467. editForm.value = {
  468. name: '',
  469. area: '',
  470. time: '',
  471. waterVolume: null,
  472. waterloggingLevel: 0
  473. }
  474. editWaterloggingPoints.value = []
  475. editWaterloggingMarkerCount.value = 0
  476. editDialogVisible.value = true
  477. }
  478. function handleEdit(row) {
  479. isEditMode.value = true
  480. editDialogTitle.value = '修改监测点'
  481. editRowId.value = row.id
  482. editForm.value = {
  483. name: row.name,
  484. area: row.area,
  485. time: row.time,
  486. waterVolume: row.waterVolume,
  487. waterloggingLevel: row.waterloggingLevel ?? 0
  488. }
  489. // 深拷贝标记点数据(waterloggingPoints 从后端返回为 JSON 字符串,需先解析为数组)
  490. editWaterloggingPoints.value = parseWaterloggingPoints(row.waterloggingPoints).map(p => ({ ...p }))
  491. editWaterloggingMarkerCount.value = editWaterloggingPoints.value.length
  492. editDialogVisible.value = true
  493. }
  494. function handleEditClose() {
  495. editDialogVisible.value = false
  496. if (editMapInstance) {
  497. editMapInstance = null
  498. }
  499. }
  500. function onEditDialogOpened() {
  501. setTimeout(() => {
  502. initEditMap()
  503. }, 300)
  504. }
  505. function initEditMap() {
  506. const container = document.getElementById('yjsdglEditMap')
  507. if (!container) return
  508. try {
  509. editMapInstance = new BMapGL.Map('yjsdglEditMap')
  510. const centerPoint = new BMapGL.Point(110.393, 28.452)
  511. editMapInstance.centerAndZoom(centerPoint, 15)
  512. editMapInstance.enableScrollWheelZoom(true)
  513. // 点击地图添加标记
  514. editMapInstance.addEventListener('click', function (e) {
  515. addEditMarker(e.latlng)
  516. })
  517. // 渲染已有标记
  518. renderEditMarkers()
  519. } catch (e) {
  520. console.error('百度地图初始化失败:', e)
  521. }
  522. }
  523. function addEditMarker(point) {
  524. // 只能标记一个点位,再次点击重新定位
  525. if (editWaterloggingPoints.value.length >= 1) {
  526. editWaterloggingPoints.value[0] = {
  527. name: editWaterloggingPoints.value[0].name,
  528. lng: point.lng,
  529. lat: point.lat
  530. }
  531. renderEditMarkers(false)
  532. return
  533. }
  534. const count = ++editWaterloggingMarkerCount.value
  535. editWaterloggingPoints.value.push({
  536. name: `易积水点${count}`,
  537. lng: point.lng,
  538. lat: point.lat
  539. })
  540. // 统一重绘,保证地图与数据完全一致
  541. renderEditMarkers(false)
  542. }
  543. function renderEditMarkers(shouldCenter = true) {
  544. if (!editMapInstance) return
  545. editMapInstance.clearOverlays()
  546. const points = editWaterloggingPoints.value
  547. if (shouldCenter) {
  548. const center = points.length > 0
  549. ? new BMapGL.Point(points[0].lng, points[0].lat)
  550. : new BMapGL.Point(110.393, 28.452)
  551. editMapInstance.centerAndZoom(center, 15)
  552. }
  553. points.forEach((point, index) => {
  554. const bPoint = new BMapGL.Point(point.lng, point.lat)
  555. const label = createMapMarker(editMapInstance, bPoint, point.name)
  556. // 右键删除标记
  557. label.addEventListener('rightclick', () => {
  558. editWaterloggingPoints.value.splice(index, 1)
  559. renderEditMarkers(false)
  560. })
  561. })
  562. }
  563. function clearEditMarkers() {
  564. editWaterloggingPoints.value = []
  565. editWaterloggingMarkerCount.value = 0
  566. renderEditMarkers()
  567. }
  568. function handleEditSubmit() {
  569. editFormRef.value.validate(async valid => {
  570. if (!valid) return
  571. const formData = {
  572. name: editForm.value.name,
  573. area: editForm.value.area,
  574. time: editForm.value.time,
  575. waterVolume: editForm.value.waterVolume,
  576. waterloggingLevel: editForm.value.waterloggingLevel,
  577. waterloggingCount: editWaterloggingPoints.value.length,
  578. waterloggingPoints: JSON.stringify(editWaterloggingPoints.value.map(p => ({ name: p.name, lng: p.lng, lat: p.lat })))
  579. }
  580. try {
  581. if (isEditMode.value) {
  582. formData.id = editRowId.value
  583. const res = await updateWaterlogging(formData)
  584. if (res.code === 0 || res.code === 200) {
  585. ElMessage.success('修改成功')
  586. } else {
  587. ElMessage.error(res.msg || '修改失败')
  588. return
  589. }
  590. } else {
  591. const res = await addWaterlogging(formData)
  592. if (res.code === 0 || res.code === 200) {
  593. ElMessage.success('新增成功')
  594. } else {
  595. ElMessage.error(res.msg || '新增失败')
  596. return
  597. }
  598. }
  599. editDialogVisible.value = false
  600. if (editMapInstance) {
  601. editMapInstance = null
  602. }
  603. loadData()
  604. } catch (e) {
  605. console.error('提交失败', e)
  606. ElMessage.error('操作失败')
  607. }
  608. })
  609. }
  610. // ==================== 删除功能 ====================
  611. function handleDelete(row) {
  612. ElMessageBox.confirm(`确认删除监测点"${row.name}"吗?`, '提示', {
  613. confirmButtonText: '确定',
  614. cancelButtonText: '取消',
  615. type: 'warning'
  616. }).then(async () => {
  617. try {
  618. const res = await deleteWaterlogging(row.id)
  619. if (res.code === 0 || res.code === 200) {
  620. ElMessage.success('删除成功')
  621. loadData()
  622. } else {
  623. ElMessage.error(res.msg || '删除失败')
  624. }
  625. } catch (e) {
  626. ElMessage.error('删除失败')
  627. }
  628. }).catch(() => { })
  629. }
  630. function handleBatchDelete() {
  631. if (selectedIds.value.length === 0) return
  632. ElMessageBox.confirm(`确认删除选中的 ${selectedIds.value.length} 条数据吗?`, '提示', {
  633. confirmButtonText: '确定',
  634. cancelButtonText: '取消',
  635. type: 'warning'
  636. }).then(async () => {
  637. try {
  638. await Promise.all(selectedIds.value.map(id => deleteWaterlogging(id)))
  639. selectedIds.value = []
  640. ElMessage.success('删除成功')
  641. loadData()
  642. } catch (e) {
  643. ElMessage.error('删除失败')
  644. }
  645. }).catch(() => { })
  646. }
  647. // ==================== 关联设备 ====================
  648. const bindDialogVisible = ref(false)
  649. const bindLoading = ref(false)
  650. const bindTableData = ref([])
  651. const bindTotal = ref(0)
  652. const bindPageNum = ref(1)
  653. const bindPageSize = ref(10)
  654. const bindQueryParams = ref({ equipmentName: '', equipmentTypeId: 'drainage' })
  655. const selectedEquipmentIds = ref([])
  656. const currentPartitionId = ref('')
  657. const bindTableRef = ref(null)
  658. function handleBind(row) {
  659. currentPartitionId.value = row.id
  660. bindDialogVisible.value = true
  661. bindPageNum.value = 1
  662. bindQueryParams.value.equipmentName = ''
  663. if (row.equipmentId) {
  664. selectedEquipmentIds.value = row.equipmentId.split(',')
  665. } else {
  666. selectedEquipmentIds.value = []
  667. }
  668. nextTick(() => {
  669. loadBindData()
  670. })
  671. }
  672. function handleBindQuery() {
  673. bindPageNum.value = 1
  674. loadBindData()
  675. }
  676. async function loadBindData() {
  677. bindLoading.value = true
  678. try {
  679. const res = await getEquipmentPage(bindPageNum.value, bindPageSize.value, bindQueryParams.value)
  680. const pageData = res.code !== undefined ? res.data : res
  681. bindTableData.value = pageData.records || []
  682. bindTotal.value = pageData.total || 0
  683. await nextTick()
  684. if (selectedEquipmentIds.value.length > 0 && bindTableRef.value) {
  685. bindTableData.value.forEach(row => {
  686. if (selectedEquipmentIds.value.includes(row.equipmentId)) {
  687. bindTableRef.value.toggleRowSelection(row, true)
  688. }
  689. })
  690. }
  691. } catch (e) {
  692. console.error('加载设备列表失败', e)
  693. } finally {
  694. bindLoading.value = false
  695. }
  696. }
  697. function handleBindSelectionChange(selection) {
  698. selectedEquipmentIds.value = selection.map(item => item.equipmentId)
  699. }
  700. function closeBindDialog() {
  701. bindDialogVisible.value = false
  702. bindTableData.value = []
  703. selectedEquipmentIds.value = []
  704. }
  705. async function submitBind() {
  706. if (selectedEquipmentIds.value.length === 0) {
  707. ElMessage.warning('请选择要关联的设备')
  708. return
  709. }
  710. try {
  711. const res = await bindWaterloggingEquipment({
  712. partitionId: currentPartitionId.value,
  713. equipmentIds: selectedEquipmentIds.value
  714. })
  715. if (res.code === 0 || res.code === 200) {
  716. ElMessage.success('关联成功')
  717. closeBindDialog()
  718. loadData()
  719. } else {
  720. ElMessage.error(res.msg || '关联失败')
  721. }
  722. } catch (e) {
  723. console.error('关联设备失败', e)
  724. ElMessage.error('关联失败')
  725. }
  726. }
  727. </script>
  728. <style scoped>
  729. .app-container {
  730. padding: 20px;
  731. }
  732. .search-form {
  733. margin-bottom: 16px;
  734. }
  735. .toolbar {
  736. margin-bottom: 16px;
  737. }
  738. .detail-info {
  739. margin-bottom: 16px;
  740. }
  741. .info-item {
  742. display: flex;
  743. align-items: center;
  744. margin-bottom: 12px;
  745. line-height: 32px;
  746. }
  747. .info-label {
  748. color: #909399;
  749. font-size: 14px;
  750. white-space: nowrap;
  751. }
  752. .info-value {
  753. color: #303133;
  754. font-size: 14px;
  755. }
  756. .map-section {
  757. border: 1px solid #e4e7ed;
  758. border-radius: 4px;
  759. overflow: hidden;
  760. }
  761. .map-tabs {
  762. display: flex;
  763. background: #f5f7fa;
  764. border-bottom: 1px solid #e4e7ed;
  765. }
  766. .map-tab {
  767. padding: 8px 24px;
  768. font-size: 14px;
  769. cursor: default;
  770. color: #fff;
  771. background: #409eff;
  772. user-select: none;
  773. }
  774. .map-tab-action {
  775. padding: 8px 24px;
  776. font-size: 14px;
  777. cursor: pointer;
  778. color: #f56c6c;
  779. transition: all 0.3s;
  780. user-select: none;
  781. margin-left: auto;
  782. }
  783. .map-tab-action:hover {
  784. color: #f78989;
  785. text-decoration: underline;
  786. }
  787. .map-container {
  788. width: 100%;
  789. height: 400px;
  790. }
  791. .marker-count {
  792. padding: 8px 16px;
  793. font-size: 13px;
  794. color: #909399;
  795. background: #f5f7fa;
  796. border-top: 1px solid #e4e7ed;
  797. }
  798. </style>