| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102 |
- <template>
- <div class="app-container">
- <!-- GIS 地图展示区 -->
- <div class="map-section" style="margin-bottom: 16px;">
- <div class="map-tabs">
- <div class="map-tab" :class="{ active: mapTab === 'all' }" @click="switchMapTab('all')">全部</div>
- <div class="map-tab" :class="{ active: mapTab === '雨水分区' }" @click="switchMapTab('雨水分区')">雨水分区</div>
- <div class="map-tab" :class="{ active: mapTab === '污水分区' }" @click="switchMapTab('污水分区')">污水分区</div>
- </div>
- <div id="psfqglMainMap" class="map-container" style="height: 350px;"></div>
- </div>
- <!-- 搜索区域 -->
- <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="80px">
- <el-form-item label="分区名称">
- <el-input v-model="queryParams.name" placeholder="请输入分区名称" clearable style="width: 200px"
- @keyup.enter="handleQuery" />
- </el-form-item>
- <el-form-item label="地点">
- <el-input v-model="queryParams.district" placeholder="请输入地点" clearable style="width: 200px"
- @keyup.enter="handleQuery" />
- </el-form-item>
- <el-form-item label="分区类型">
- <el-select v-model="queryParams.partitionType" placeholder="请选择分区类型" clearable style="width: 200px">
- <el-option label="雨水分区" value="雨水分区" />
- <el-option label="污水分区" value="污水分区" />
- </el-select>
- </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-row :gutter="10" class="mb8">
- <el-col :span="1.5">
- <el-button type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
- </el-col>
- <el-col :span="1.5">
- <el-button type="danger" plain icon="Delete" :disabled="selectedIds.length === 0"
- @click="handleBatchDelete">删除</el-button>
- </el-col>
- <right-toolbar v-model:showSearch="showSearch" @queryTable="loadData"></right-toolbar>
- </el-row>
- <!-- 数据表格 -->
- <el-table v-loading="loading" :data="filteredList" style="width: 100%" @selection-change="handleSelectionChange">
- <el-table-column type="selection" width="55" align="center" />
- <el-table-column label="分区名称" prop="name" min-width="160" />
- <el-table-column label="地点" prop="district" min-width="100" />
- <el-table-column label="分区类型" prop="partitionType" min-width="100" />
- <el-table-column label="数据时间" prop="time" min-width="120" />
- <el-table-column :label="`排水量(${DRAINAGE_UNIT})`" prop="drainage" min-width="120" align="right">
- <template #default="{ row }">
- <span>{{ formatVolume(row.drainage) }}</span>
- </template>
- </el-table-column>
- <el-table-column :label="`积水量(${WATERLOGGING_UNIT})`" prop="waterlogging" min-width="120" align="right">
- <template #default="{ row }">
- <span>{{ formatVolume(row.waterlogging) }}</span>
- </template>
- </el-table-column>
- <el-table-column label="积水等级" prop="level" min-width="110" align="center">
- <template #default="{ row }">
- <el-tag v-if="hasLevel(row.level)" :type="getLevelTagType(row.level)" effect="light" size="small">
- {{ getLevelLabel(row.level) }}
- </el-tag>
- <span v-else class="text-placeholder">--</span>
- </template>
- </el-table-column>
- <el-table-column label="关联设备" min-width="160">
- <template #default="{ row }">
- {{ equipmentNameMap[row.id] || '' }}
- </template>
- </el-table-column>
- <el-table-column label="操作" width="240" align="center">
- <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="primary" @click="handleBind(row)">关联设备</el-button>
- <el-button link type="danger" @click="handleDelete(row)">删除</el-button>
- </template>
- </el-table-column>
- </el-table>
- <!-- 分页 -->
- <pagination v-show="total > 0" :total="total" v-model:page="pageNum" v-model:limit="pageSize"
- @pagination="loadData" />
- <!-- 查看详情对话框 -->
- <el-drawer v-model="dialogVisible" title="详情" size="700px" :before-close="handleClose" @opened="onDialogOpened" 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.name }}</span>
- </div>
- </el-col>
- <el-col :span="12">
- <div class="info-item">
- <span class="info-label">地点:</span>
- <span class="info-value">{{ currentRow.district }}</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.time }}</span>
- </div>
- </el-col>
- <el-col :span="12">
- <div class="info-item">
- <span class="info-label">排水量:</span>
- <span class="info-value">{{ formatVolume(currentRow.drainage) }} {{ DRAINAGE_UNIT }}</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">{{ formatVolume(currentRow.waterlogging) }} {{ WATERLOGGING_UNIT }}</span>
- </div>
- </el-col>
- <el-col :span="12">
- <div class="info-item">
- <span class="info-label">积水等级:</span>
- <span class="info-value">
- <el-tag v-if="hasLevel(currentRow.level)" :type="getLevelTagType(currentRow.level)" effect="light" size="small">
- {{ getLevelLabel(currentRow.level) }}
- </el-tag>
- <span v-else class="text-placeholder">--</span>
- </span>
- </div>
- </el-col>
- </el-row>
- </div>
- <!-- 关联设备 -->
- <div v-if="viewEquipmentNames.length" class="detail-info" style="margin-top: 12px;">
- <el-row :gutter="20">
- <el-col :span="24">
- <div class="info-item">
- <span class="info-label">关联设备:</span>
- <span class="info-value">{{ viewEquipmentNames.join('、') }}</span>
- </div>
- </el-col>
- </el-row>
- </div>
- <!-- 地图区域 -->
- <div class="map-section">
- <div class="map-tabs">
- <div class="map-tab active">分区面预览</div>
- </div>
- <div id="psfqglViewMap" class="map-container"></div>
- </div>
- </el-drawer>
- <!-- 新增/修改对话框 -->
- <el-dialog v-model="editDialogVisible" :title="editDialogTitle" width="800px" :before-close="handleEditClose"
- @opened="onEditDialogOpened">
- <el-form ref="editFormRef" :model="editForm" :rules="editRules" label-width="100px">
- <el-row :gutter="20">
- <el-col :span="12">
- <el-form-item label="分区名称" prop="name">
- <el-input v-model="editForm.name" placeholder="请输入分区名称" />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="地点" prop="district">
- <el-input v-model="editForm.district" placeholder="请输入地点" />
- </el-form-item>
- </el-col>
- </el-row>
- <el-row :gutter="20">
- <el-col :span="12">
- <el-form-item label="数据时间" prop="time">
- <el-date-picker v-model="editForm.time" type="date" placeholder="选择日期" value-format="YYYY-MM-DD"
- style="width: 100%" />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="分区类型" prop="partitionType">
- <el-select v-model="editForm.partitionType" placeholder="请选择分区类型" style="width:100%"
- @change="handlePartitionTypeChange">
- <el-option label="雨水分区" value="雨水分区" />
- <el-option label="污水分区" value="污水分区" />
- </el-select>
- </el-form-item>
- </el-col>
- </el-row>
- <el-row :gutter="20">
- <el-col :span="12">
- <el-form-item :label="`排水量(${DRAINAGE_UNIT})`" prop="drainage">
- <el-input-number v-model="editForm.drainage" :precision="2" :min="0" style="width:100%"
- placeholder="请输入排水量" />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item :label="`积水量(${WATERLOGGING_UNIT})`" prop="waterlogging">
- <el-input-number v-model="editForm.waterlogging" :precision="2" :min="0" style="width:100%"
- placeholder="请输入积水量" />
- </el-form-item>
- </el-col>
- </el-row>
- <el-row :gutter="20">
- <el-col :span="12">
- <el-form-item label="积水等级" prop="level">
- <el-select v-model="editForm.level" placeholder="请选择积水等级" clearable style="width:100%">
- <el-option v-for="item in levelOptions" :key="item.value" :label="item.label"
- :value="normalizeLevelValue(item.value)" />
- </el-select>
- </el-form-item>
- </el-col>
- </el-row>
- </el-form>
- <!-- 地图标点区域 -->
- <div class="map-section" style="margin-top: 16px;">
- <div class="map-tabs">
- <div class="map-tab active">分区面绘制</div>
- <div v-if="isPolygonPartition" class="map-tab-action" @click="togglePolygonDrawing">
- {{ isDrawingPolygon ? '完成绘制' : '开始绘制' }}
- </div>
- <div class="map-tab-action" @click="clearEditMarkers">清除边界</div>
- </div>
- <div id="psfqglEditMap" class="map-container" style="height: 350px;"></div>
- <div class="marker-count">
- {{ isPolygonPartition ? `已绘制 ${editPolygonPoints.length} 个点` : '请选择分区类型后绘制面' }}
- </div>
- </div>
- <template #footer>
- <el-button @click="handleEditClose">取 消</el-button>
- <el-button type="primary" @click="handleEditSubmit">确 定</el-button>
- </template>
- </el-dialog>
- <!-- 关联设备对话框 -->
- <el-dialog v-model="bindDialogVisible" title="关联设备" width="800px">
- <el-form :model="bindQueryParams" :inline="true" class="search-form">
- <el-form-item label="设备名称">
- <el-input v-model="bindQueryParams.equipmentName" placeholder="请输入设备名称" clearable style="width: 220px" />
- </el-form-item>
- <el-form-item>
- <el-button type="primary" @click="handleBindQuery">搜索</el-button>
- </el-form-item>
- </el-form>
- <el-table ref="bindTableRef" v-loading="bindLoading" :data="bindTableData" row-key="equipmentId" border
- style="width: 100%" height="400">
- <el-table-column label="选择" width="55" align="center">
- <template #default="{ row }">
- <el-checkbox :model-value="selectedEquipmentIds.includes(String(row.equipmentId))"
- :disabled="isEquipmentBound(row)" @change="toggleEquipment(row, $event)" />
- </template>
- </el-table-column>
- <el-table-column label="设备名称" prop="equipmentName" min-width="160" />
- <el-table-column label="设备编号" prop="equipmentCode" min-width="140" />
- <el-table-column label="设备类型" prop="equipmentTypeName" min-width="120" />
- <el-table-column label="关联状态" min-width="180">
- <template #default="{ row }">
- <span v-if="selectedEquipmentIds.includes(String(row.equipmentId))" style="color: #67c23a;">当前选择</span>
- <span v-else-if="boundEquipmentMap[String(row.equipmentId)]" style="color: #e6a23c;">
- 已关联:{{ boundEquipmentMap[String(row.equipmentId)] }}
- </span>
- <span v-else>未关联</span>
- </template>
- </el-table-column>
- </el-table>
- <pagination v-show="bindTotal > 0" v-model:page="bindPageNum" v-model:limit="bindPageSize" :total="bindTotal"
- @pagination="loadBindData" />
- <template #footer>
- <el-button @click="closeBindDialog">取 消</el-button>
- <el-button type="primary" @click="submitBind">确 定</el-button>
- </template>
- </el-dialog>
- </div>
- </template>
- <script setup name="Psfqgl">
- import { ref, computed, nextTick, onMounted } from 'vue'
- import { ElMessage, ElMessageBox } from 'element-plus'
- import { useDict } from '@/utils/dict'
- import locationIcon from '@/assets/images/location.png'
- import {
- getPartitionPage,
- addPartition,
- updatePartition,
- deletePartition,
- batchDeletePartition,
- getEquipmentPage,
- getEquipmentById,
- bindPartitionEquipment,
- getDrainageBoundEquipmentIds
- } from '@/api/drainage'
- // ==================== 量纲 + 积水等级字典 ====================
- // 排水量 / 积水量 单位:业务口径若调整,只改这两处,表单、列表、详情同步生效
- const DRAINAGE_UNIT = 'm³'
- const WATERLOGGING_UNIT = 'm³'
- // 积水等级字典(sys_dict_type = drainage_waterlogging_level)
- // 与易积水点 waterlogging_level 口径保持一致:0未积水 / 1轻度 / 2中度 / 3严重
- const { drainage_waterlogging_level: levelDict } = useDict('drainage_waterlogging_level')
- // 字典表未配置时用内置口径兜底,避免页面无选项
- const LEVEL_FALLBACK = [
- { label: '未积水', value: '0', elTagType: 'success' },
- { label: '轻度积水', value: '1', elTagType: 'primary' },
- { label: '中度积水', value: '2', elTagType: 'warning' },
- { label: '严重积水', value: '3', elTagType: 'danger' }
- ]
- const levelOptions = computed(() => {
- const list = levelDict.value
- return Array.isArray(list) && list.length ? list : LEVEL_FALLBACK
- })
- // 字典值统一成数字,保证 el-select 回显能与后端 Integer 字段匹配
- function normalizeLevelValue(value) {
- if (value === null || value === undefined || value === '') return value
- return Number.isNaN(Number(value)) ? value : Number(value)
- }
- function hasLevel(value) {
- return value !== null && value !== undefined && value !== ''
- }
- function findLevelOption(value) {
- const key = String(value)
- return levelOptions.value.find(item => String(item.value) === key)
- }
- function getLevelLabel(value) {
- const option = findLevelOption(value)
- return option ? option.label : '--'
- }
- // 标签配色由字典 list_class 决定;primary / 空值走默认主题色
- function getLevelTagType(value) {
- const option = findLevelOption(value)
- const type = option ? (option.elTagType || option.listClass) : ''
- return !type || type === 'primary' || type === 'default' ? '' : type
- }
- function formatVolume(value) {
- if (value === null || value === undefined || value === '') return '--'
- const num = Number(value)
- return Number.isFinite(num) ? num.toFixed(2) : String(value)
- }
- // 地图标记创建(用Label+img代替Marker)
- function createMapMarker(map, bPoint, name, bgColor) {
- const color = bgColor || '#409eff'
- const html = `<div style="text-align:center;cursor:pointer;">
- <div style="color:#fff;background:${color};border-radius:4px;padding:2px 6px;font-size:11px;white-space:nowrap;display:inline-block;margin-bottom:2px;">${name || ''}</div>
- <div><img src="${locationIcon}" style="width:28px;height:28px;display:block;margin:0 auto;"/></div>
- </div>`
- const label = new TdtMap.Label(html, {
- position: bPoint,
- offset: new TdtMap.Size(-30, -50)
- })
- label.setStyle({
- border: 'none',
- background: 'transparent',
- padding: '0'
- })
- map.addOverlay(label)
- return label
- }
- // ==================== 顶部地图 ====================
- let mainMapInstance = null
- const mainMapMarkers = ref([])
- const mapTab = ref('all')
- function parsePolygonPoints(value) {
- if (!value) return []
- try {
- const points = typeof value === 'string' ? JSON.parse(value) : value
- return Array.isArray(points)
- ? points.map(point => ({ lng: Number(point.lng), lat: Number(point.lat) }))
- .filter(point => Number.isFinite(point.lng) && Number.isFinite(point.lat))
- : []
- } catch (e) {
- return []
- }
- }
- function getPartitionCenter(item) {
- const points = parsePolygonPoints(item.points)
- if (points.length) {
- const lng = points.reduce((sum, point) => sum + point.lng, 0) / points.length
- const lat = points.reduce((sum, point) => sum + point.lat, 0) / points.length
- return { lng, lat }
- }
- return { lng: Number(item.longitude), lat: Number(item.latitude) }
- }
- function switchMapTab(tab) {
- mapTab.value = tab
- renderMainMapMarkers()
- }
- function initMainMap() {
- const container = document.getElementById('psfqglMainMap')
- if (!container) return
- try {
- mainMapInstance = new TdtMap.Map('psfqglMainMap')
- const centerPoint = new TdtMap.Point(110.393, 28.452)
- mainMapInstance.centerAndZoom(centerPoint, 12)
- mainMapInstance.enableScrollWheelZoom(true)
- renderMainMapMarkers()
- } catch (e) {
- console.error('顶部天地图初始化失败:', e)
- }
- }
- function renderMainMapMarkers() {
- if (!mainMapInstance) return
- // 清除旧标记
- mainMapMarkers.value.forEach(label => {
- mainMapInstance.removeOverlay(label)
- })
- mainMapMarkers.value = []
- const list = tableData.value.filter(item => parsePolygonPoints(item.points).length >= 3 || (item.longitude && item.latitude))
- const filtered = mapTab.value === 'all'
- ? list
- : list.filter(item => item.partitionType === mapTab.value)
- filtered.forEach(item => {
- const color = item.partitionType === '污水分区' ? '#e6a23c' : '#409eff'
- const points = parsePolygonPoints(item.points)
- if (points.length >= 3) {
- const polygon = new TdtMap.Polygon(points.map(point => new TdtMap.Point(point.lng, point.lat)), {
- strokeColor: color,
- fillColor: color,
- strokeWeight: 2,
- strokeOpacity: 0.8,
- fillOpacity: 0.25
- })
- mainMapInstance.addOverlay(polygon)
- mainMapMarkers.value.push(polygon)
- const center = getPartitionCenter(item)
- const label = createMapMarker(mainMapInstance, new TdtMap.Point(center.lng, center.lat), item.name, color)
- mainMapMarkers.value.push(label)
- } else {
- const bPoint = new TdtMap.Point(item.longitude, item.latitude)
- const label = createMapMarker(mainMapInstance, bPoint, item.name, color)
- mainMapMarkers.value.push(label)
- }
- })
- }
- // ==================== 搜索参数 ====================
- const showSearch = ref(true)
- const queryRef = ref()
- const queryParams = ref({
- name: '',
- district: '',
- partitionType: ''
- })
- // ==================== 表格数据 ====================
- const tableData = ref([])
- const total = ref(0)
- const pageNum = ref(1)
- const pageSize = ref(10)
- const loading = ref(false)
- // 加载分页数据
- async function loadData() {
- loading.value = true
- try {
- const res = await getPartitionPage(pageNum.value, pageSize.value, queryParams.value)
- // 兼容直接返回Page对象(无code包装)和AjaxResult包装两种格式
- const pageData = res.code !== undefined ? res.data : res
- tableData.value = pageData.records || []
- total.value = pageData.total || 0
- renderMainMapMarkers()
- // 解析关联设备名称
- resolveEquipmentNames()
- } catch (e) {
- console.error('加载排水分区数据失败', e)
- } finally {
- loading.value = false
- }
- }
- // 关联设备名称映射(分区ID -> 设备名称字符串)
- const equipmentNameMap = ref({})
- async function resolveEquipmentNames() {
- const map = {}
- for (const row of tableData.value) {
- if (row.equipmentId) {
- const ids = row.equipmentId.split(',')
- const names = []
- for (const id of ids) {
- try {
- const res = await getEquipmentById(id.trim())
- const data = res.code !== undefined ? res.data : res
- const info = data && data.equipmentInfo ? data.equipmentInfo : data
- if (info && info.equipmentName) names.push(info.equipmentName)
- } catch (e) { /* skip */ }
- }
- map[row.id] = names.join('、')
- }
- }
- equipmentNameMap.value = map
- }
- onMounted(() => {
- loadData()
- initMainMap()
- })
- // 多选
- const selectedIds = ref([])
- function handleSelectionChange(selection) {
- selectedIds.value = selection.map(item => item.id)
- }
- // 表格数据直接用 tableData(已是分页数据)
- const filteredList = computed(() => tableData.value)
- // 搜索
- function handleQuery() {
- pageNum.value = 1
- loadData()
- }
- // 重置搜索条件
- function resetQuery() {
- queryRef.value?.resetFields()
- queryParams.value.name = ''
- queryParams.value.district = ''
- queryParams.value.partitionType = ''
- pageNum.value = 1
- loadData()
- }
- // ==================== 查看详情对话框 ====================
- const dialogVisible = ref(false)
- const currentRow = ref({})
- let viewMapInstance = null
- const viewEquipmentNames = ref([])
- async function handleView(row) {
- currentRow.value = { ...row }
- dialogVisible.value = true
- // 解析关联设备名称
- viewEquipmentNames.value = []
- if (row.equipmentId) {
- const ids = row.equipmentId.split(',')
- const names = []
- for (const id of ids) {
- try {
- const res = await getEquipmentById(id.trim())
- const data = res.code !== undefined ? res.data : res
- const info = data && data.equipmentInfo ? data.equipmentInfo : data
- if (info && info.equipmentName) names.push(info.equipmentName)
- } catch (e) { /* skip */ }
- }
- viewEquipmentNames.value = names
- }
- }
- function handleClose() {
- dialogVisible.value = false
- if (viewMapInstance) {
- viewMapInstance = null
- }
- }
- function onDialogOpened() {
- setTimeout(() => {
- initViewMap()
- }, 300)
- }
- function initViewMap() {
- const container = document.getElementById('psfqglViewMap')
- if (!container) return
- try {
- viewMapInstance = new TdtMap.Map('psfqglViewMap')
- const center = getPartitionCenter(currentRow.value)
- const centerLng = center.lng || 110.393
- const centerLat = center.lat || 28.452
- const centerPoint = new TdtMap.Point(centerLng, centerLat)
- viewMapInstance.centerAndZoom(centerPoint, 15)
- viewMapInstance.enableScrollWheelZoom(true)
- const points = parsePolygonPoints(currentRow.value.points)
- if (points.length >= 3) {
- viewMapInstance.addOverlay(new TdtMap.Polygon(points.map(point => new TdtMap.Point(point.lng, point.lat)), {
- strokeColor: '#409eff', fillColor: '#409eff', strokeWeight: 2, fillOpacity: 0.25
- }))
- } else if (currentRow.value.longitude && currentRow.value.latitude) {
- createMapMarker(viewMapInstance, new TdtMap.Point(currentRow.value.longitude, currentRow.value.latitude), currentRow.value.name)
- }
- } catch (e) {
- console.error('天地图初始化失败:', e)
- }
- }
- // ==================== 新增/修改对话框 ====================
- const editDialogVisible = ref(false)
- const editDialogTitle = ref('新增排水分区')
- const editFormRef = ref(null)
- const isEditMode = ref(false)
- const editRowId = ref(null)
- const editForm = ref({
- name: '',
- district: '',
- partitionType: '',
- time: '',
- drainage: 0,
- waterlogging: 0,
- level: 0,
- longitude: null,
- latitude: null,
- points: ''
- })
- const editRules = {
- name: [{ required: true, message: '请输入分区名称', trigger: 'blur' }],
- time: [{ required: true, message: '请选择数据时间', trigger: 'change' }],
- level: [{ required: true, message: '请选择积水等级', trigger: 'change' }]
- }
- // 编辑弹窗地图相关
- let editMapInstance = null
- let editMarkerLabel = null
- let editPolygonOverlay = null
- const editPolygonPoints = ref([])
- const isDrawingPolygon = ref(false)
- const editPointOverlays = ref([])
- const polygonPartitionTypes = ['雨水分区', '污水分区']
- const isPolygonPartition = computed(() => polygonPartitionTypes.includes(editForm.value.partitionType))
- function handleAdd() {
- isEditMode.value = false
- editDialogTitle.value = '新增排水分区'
- editRowId.value = null
- editForm.value = {
- name: '',
- district: '',
- partitionType: '',
- time: '',
- drainage: 0,
- waterlogging: 0,
- level: 0,
- longitude: null,
- latitude: null,
- points: ''
- }
- editDialogVisible.value = true
- }
- function handleEdit(row) {
- isEditMode.value = true
- editDialogTitle.value = '修改排水分区'
- editRowId.value = row.id
- editForm.value = {
- name: row.name,
- district: row.district,
- partitionType: row.partitionType,
- time: row.time,
- drainage: row.drainage,
- waterlogging: row.waterlogging,
- level: hasLevel(row.level) ? normalizeLevelValue(row.level) : 0,
- longitude: row.longitude,
- latitude: row.latitude,
- points: row.points || ''
- }
- editDialogVisible.value = true
- }
- function handleEditClose() {
- editDialogVisible.value = false
- if (editMapInstance) {
- editMapInstance = null
- }
- editMarkerLabel = null
- editPolygonOverlay = null
- editPointOverlays.value = []
- editPolygonPoints.value = []
- isDrawingPolygon.value = false
- }
- function onEditDialogOpened() {
- setTimeout(() => {
- initEditMap()
- }, 300)
- }
- function initEditMap() {
- const container = document.getElementById('psfqglEditMap')
- if (!container) return
- try {
- editMapInstance = new TdtMap.Map('psfqglEditMap')
- const centerLng = editForm.value.longitude || 110.393
- const centerLat = editForm.value.latitude || 28.452
- const centerPoint = new TdtMap.Point(centerLng, centerLat)
- editMapInstance.centerAndZoom(centerPoint, 15)
- editMapInstance.enableScrollWheelZoom(true)
- editPolygonPoints.value = parsePolygonPoints(editForm.value.points)
- renderEditPolygon()
- editMapInstance.addEventListener('click', function (e) {
- if (!isDrawingPolygon.value || !isPolygonPartition.value) return
- const point = e.latLng || e.latlng
- if (!point) return
- editPolygonPoints.value.push({ lng: point.lng, lat: point.lat })
- renderEditPolygon()
- })
- } catch (e) {
- console.error('天地图初始化失败:', e)
- }
- }
- function clearEditMarkers() {
- if (editMapInstance && editPolygonOverlay) editMapInstance.removeOverlay(editPolygonOverlay)
- if (editMapInstance && editMarkerLabel) editMapInstance.removeOverlay(editMarkerLabel)
- if (editMapInstance) {
- editPointOverlays.value.forEach(overlay => editMapInstance.removeOverlay(overlay))
- }
- editPolygonOverlay = null
- editMarkerLabel = null
- editPointOverlays.value = []
- editPolygonPoints.value = []
- editForm.value.points = ''
- editForm.value.longitude = null
- editForm.value.latitude = null
- isDrawingPolygon.value = false
- }
- function handlePartitionTypeChange(type) {
- if (!polygonPartitionTypes.includes(type)) clearEditMarkers()
- }
- function renderEditPolygon() {
- if (!editMapInstance) return
- if (editPolygonOverlay) editMapInstance.removeOverlay(editPolygonOverlay)
- editPointOverlays.value.forEach(overlay => editMapInstance.removeOverlay(overlay))
- editPointOverlays.value = []
- editPolygonPoints.value.forEach(point => {
- const overlay = new TdtMap.Circle(new TdtMap.Point(point.lng, point.lat), 5, {
- strokeColor: '#ffffff',
- strokeWeight: 2,
- strokeOpacity: 1,
- fillColor: '#409eff',
- fillOpacity: 1
- })
- editMapInstance.addOverlay(overlay)
- editPointOverlays.value.push(overlay)
- })
- if (editPolygonPoints.value.length < 2) return
- const points = editPolygonPoints.value.map(point => new TdtMap.Point(point.lng, point.lat))
- const OverlayType = editPolygonPoints.value.length >= 3 ? TdtMap.Polygon : TdtMap.Polyline
- editPolygonOverlay = new OverlayType(points, {
- strokeColor: '#409eff',
- strokeWeight: 2,
- strokeOpacity: 0.9,
- fillColor: '#409eff',
- fillOpacity: 0.25
- })
- editMapInstance.addOverlay(editPolygonOverlay)
- }
- function togglePolygonDrawing() {
- if (!isPolygonPartition.value) return
- if (isDrawingPolygon.value && editPolygonPoints.value.length < 3) {
- ElMessage.warning('分区面至少需要绘制3个点')
- return
- }
- isDrawingPolygon.value = !isDrawingPolygon.value
- if (!isDrawingPolygon.value) finalizePolygon()
- }
- function finalizePolygon() {
- if (editPolygonPoints.value.length < 3) return false
- const points = editPolygonPoints.value.map(point => ({
- lng: Number(point.lng.toFixed(8)),
- lat: Number(point.lat.toFixed(8))
- }))
- editPolygonPoints.value = points
- editForm.value.points = JSON.stringify(points)
- editForm.value.longitude = points.reduce((sum, point) => sum + point.lng, 0) / points.length
- editForm.value.latitude = points.reduce((sum, point) => sum + point.lat, 0) / points.length
- renderEditPolygon()
- return true
- }
- function handleEditSubmit() {
- editFormRef.value.validate(async valid => {
- if (!valid) return
- if (isPolygonPartition.value) {
- if (isDrawingPolygon.value) finalizePolygon()
- if (editPolygonPoints.value.length < 3) {
- ElMessage.warning('分区面至少需要在地图上绘制3个点')
- return
- }
- }
- const formData = {
- ...editForm.value
- }
- try {
- if (isEditMode.value) {
- formData.id = editRowId.value
- const res = await updatePartition(formData)
- if (res.code === 0 || res.code === 200) {
- ElMessage.success('修改成功')
- } else {
- ElMessage.error(res.msg || '修改失败')
- return
- }
- } else {
- const res = await addPartition(formData)
- if (res.code === 0 || res.code === 200) {
- ElMessage.success('新增成功')
- } else {
- ElMessage.error(res.msg || '新增失败')
- return
- }
- }
- editDialogVisible.value = false
- if (editMapInstance) {
- editMapInstance = null
- }
- editMarkerLabel = null
- loadData()
- } catch (e) {
- console.error('提交失败', e)
- ElMessage.error('操作失败')
- }
- })
- }
- // ==================== 删除功能 ====================
- function handleDelete(row) {
- ElMessageBox.confirm(`确认删除监测点"${row.name}"吗?`, '提示', {
- confirmButtonText: '确定',
- cancelButtonText: '取消',
- type: 'warning'
- }).then(async () => {
- try {
- const res = await deletePartition(row.id)
- if (res.code === 0 || res.code === 200) {
- ElMessage.success('删除成功')
- loadData()
- } else {
- ElMessage.error(res.msg || '删除失败')
- }
- } catch (e) {
- 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 batchDeletePartition(selectedIds.value)
- if (res.code === 0 || res.code === 200) {
- selectedIds.value = []
- ElMessage.success('删除成功')
- loadData()
- } else {
- ElMessage.error(res.msg || '删除失败')
- }
- } catch (e) {
- ElMessage.error('删除失败')
- }
- }).catch(() => { })
- }
- // ==================== 关联设备 ====================
- const bindDialogVisible = ref(false)
- const currentPartitionId = ref(null)
- const bindTableRef = ref(null)
- const bindQueryParams = ref({ equipmentName: '', equipmentTypeId: 'drainage' })
- const bindTableData = ref([])
- const bindTotal = ref(0)
- const bindPageNum = ref(1)
- const bindPageSize = ref(10)
- const bindLoading = ref(false)
- const selectedEquipmentIds = ref([])
- const boundEquipmentMap = ref({})
- async function handleBind(row) {
- currentPartitionId.value = row.id
- bindDialogVisible.value = true
- bindPageNum.value = 1
- bindQueryParams.value.equipmentName = ''
- selectedEquipmentIds.value = row.equipmentId
- ? row.equipmentId.split(',').map(id => id.trim()).filter(Boolean)
- : []
- nextTick(() => {
- loadBindData()
- })
- }
- function handleBindQuery() {
- bindPageNum.value = 1
- loadBindData()
- }
- async function loadBindData() {
- bindLoading.value = true
- try {
- const [equipmentResult, boundResult] = await Promise.allSettled([
- getEquipmentPage(bindPageNum.value, bindPageSize.value, bindQueryParams.value),
- getDrainageBoundEquipmentIds('partition', currentPartitionId.value)
- ])
- if (equipmentResult.status === 'rejected') {
- throw equipmentResult.reason
- }
- const res = equipmentResult.value
- const pageData = res.code !== undefined ? res.data : res
- bindTableData.value = pageData.records || []
- bindTotal.value = pageData.total || 0
- if (boundResult.status === 'fulfilled') {
- const boundRes = boundResult.value
- const boundData = boundRes.code !== undefined ? boundRes.data : boundRes
- boundEquipmentMap.value = boundData && typeof boundData === 'object' ? boundData : {}
- } else {
- boundEquipmentMap.value = {}
- console.warn('加载设备关联状态失败,设备列表仍正常显示', boundResult.reason)
- }
- } catch (e) {
- bindTableData.value = []
- bindTotal.value = 0
- console.error('加载设备列表失败', e)
- } finally {
- bindLoading.value = false
- }
- }
- function closeBindDialog() {
- bindDialogVisible.value = false
- currentPartitionId.value = null
- selectedEquipmentIds.value = []
- boundEquipmentMap.value = {}
- }
- function isEquipmentBound(row) {
- const id = String(row.equipmentId)
- return Boolean(boundEquipmentMap.value[id]) && !selectedEquipmentIds.value.includes(id)
- }
- function toggleEquipment(row, checked) {
- const id = String(row.equipmentId)
- if (isEquipmentBound(row)) return
- const ids = new Set(selectedEquipmentIds.value)
- if (checked) ids.add(id)
- else ids.delete(id)
- selectedEquipmentIds.value = [...ids]
- }
- async function submitBind() {
- if (!selectedEquipmentIds.value.length) {
- ElMessage.warning('请选择要关联的设备')
- return
- }
- try {
- const res = await bindPartitionEquipment({
- partitionId: currentPartitionId.value,
- equipmentIds: selectedEquipmentIds.value
- })
- if (res.code === 0 || res.code === 200) {
- ElMessage.success('关联成功')
- closeBindDialog()
- loadData()
- } else {
- ElMessage.error(res.msg || '关联失败')
- }
- } catch (e) {
- console.error('关联设备失败', e)
- ElMessage.error('关联失败')
- }
- }
- </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;
- }
- .text-placeholder {
- color: #c0c4cc;
- }
- .map-section {
- border: 1px solid #e4e7ed;
- border-radius: 4px;
- overflow: hidden;
- }
- .map-tabs {
- display: flex;
- background: #f5f7fa;
- border-bottom: 1px solid #e4e7ed;
- }
- .map-tab {
- padding: 8px 24px;
- font-size: 14px;
- cursor: pointer;
- color: #606266;
- transition: all 0.3s;
- user-select: none;
- }
- .map-tab:hover {
- color: #409eff;
- }
- .map-tab.active {
- background: #409eff;
- color: #fff;
- }
- .map-tab-action {
- padding: 8px 24px;
- font-size: 14px;
- cursor: pointer;
- color: #f56c6c;
- transition: all 0.3s;
- user-select: none;
- margin-left: auto;
- }
- .map-tab-action:hover {
- color: #f78989;
- text-decoration: underline;
- }
- .map-container {
- width: 100%;
- height: 400px;
- }
- .marker-count {
- padding: 8px 16px;
- font-size: 13px;
- color: #909399;
- background: #f5f7fa;
- border-top: 1px solid #e4e7ed;
- }
- </style>
|