|
|
@@ -0,0 +1,1211 @@
|
|
|
+<template>
|
|
|
+ <div class="app-container">
|
|
|
+ <!-- 顶部统计卡片区 -->
|
|
|
+ <div class="stat-cards">
|
|
|
+ <el-card class="stat-card stat-blue" shadow="hover">
|
|
|
+ <div class="stat-card-inner">
|
|
|
+ <div class="stat-icon"><el-icon :size="36"><Monitor /></el-icon></div>
|
|
|
+ <div class="stat-info">
|
|
|
+ <div class="stat-value">{{ stats.total }}</div>
|
|
|
+ <div class="stat-label">设备总数</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+ <el-card class="stat-card stat-green" shadow="hover">
|
|
|
+ <div class="stat-card-inner">
|
|
|
+ <div class="stat-icon"><el-icon :size="36"><CircleCheck /></el-icon></div>
|
|
|
+ <div class="stat-info">
|
|
|
+ <div class="stat-value">{{ stats.online }}</div>
|
|
|
+ <div class="stat-label">在线设备</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+ <el-card class="stat-card stat-gray" shadow="hover">
|
|
|
+ <div class="stat-card-inner">
|
|
|
+ <div class="stat-icon"><el-icon :size="36"><SwitchButton /></el-icon></div>
|
|
|
+ <div class="stat-info">
|
|
|
+ <div class="stat-value">{{ stats.offline }}</div>
|
|
|
+ <div class="stat-label">离线设备</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+ <el-card class="stat-card stat-red" shadow="hover">
|
|
|
+ <div class="stat-card-inner">
|
|
|
+ <div class="stat-icon"><el-icon :size="36"><Warning /></el-icon></div>
|
|
|
+ <div class="stat-info">
|
|
|
+ <div class="stat-value">{{ stats.alarm }}</div>
|
|
|
+ <div class="stat-label">报警设备</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+ <el-card class="stat-card stat-cyan" shadow="hover">
|
|
|
+ <div class="stat-card-inner">
|
|
|
+ <div class="stat-icon"><el-icon :size="36"><DataLine /></el-icon></div>
|
|
|
+ <div class="stat-info">
|
|
|
+ <div class="stat-value">{{ stats.onlineRate }}%</div>
|
|
|
+ <div class="stat-label">在线率</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 搜索/筛选区域 -->
|
|
|
+ <el-form :model="queryParams" ref="queryRef" :inline="true" class="search-form">
|
|
|
+ <el-form-item label="设备名称">
|
|
|
+ <el-input v-model="queryParams.name" placeholder="请输入" clearable style="width: 200px" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="设备类型">
|
|
|
+ <el-select v-model="queryParams.type" placeholder="请选择" clearable style="width: 200px">
|
|
|
+ <el-option v-for="item in typeOptions" :key="item.value" :label="item.label" :value="item.value" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <template v-if="showMoreSearch">
|
|
|
+ <el-form-item label="监测状态">
|
|
|
+ <el-select v-model="queryParams.status" placeholder="请选择" clearable style="width: 200px">
|
|
|
+ <el-option v-for="item in monitorStatusOptions" :key="item.value" :label="item.label" :value="item.value" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="所属区域">
|
|
|
+ <el-select v-model="queryParams.area" placeholder="请选择" clearable style="width: 200px">
|
|
|
+ <el-option v-for="item in areaOptions" :key="item.value" :label="item.label" :value="item.value" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ </template>
|
|
|
+ <el-form-item>
|
|
|
+ <el-button type="primary" @click="handleQuery">搜索</el-button>
|
|
|
+ <el-button @click="resetQuery">重置</el-button>
|
|
|
+ <el-button link type="primary" @click="showMoreSearch = !showMoreSearch">
|
|
|
+ {{ showMoreSearch ? '收起' : '展开' }}
|
|
|
+ <el-icon class="expand-icon" :class="{ 'is-rotate': showMoreSearch }"><ArrowUp /></el-icon>
|
|
|
+ </el-button>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+
|
|
|
+ <!-- 操作按钮区 -->
|
|
|
+ <div class="toolbar">
|
|
|
+ <el-button type="primary" @click="handleAdd">新增</el-button>
|
|
|
+ <el-button type="danger" :disabled="selectedRows.length === 0" @click="handleBatchDelete">删除</el-button>
|
|
|
+ <el-button type="warning" @click="handleExport">导出</el-button>
|
|
|
+ <el-button type="primary" :disabled="selectedRows.length === 0" @click="handleBatchRepair">维修派单</el-button>
|
|
|
+ <el-button type="danger" :disabled="selectedRows.length === 0" @click="handleBatchWarning">发布预警</el-button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 实时数据表格 -->
|
|
|
+ <el-table :data="filteredList" border style="width: 100%" @selection-change="handleSelectionChange">
|
|
|
+ <el-table-column type="selection" width="55" align="center" />
|
|
|
+ <el-table-column label="设备名称" prop="name" min-width="140" />
|
|
|
+ <el-table-column label="设备编号" prop="code" min-width="130" />
|
|
|
+ <el-table-column label="设备类型" prop="type" min-width="110" align="center">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <span>{{ typeText(row.type) }}</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="所属监测点" prop="monitorPoint" min-width="140" />
|
|
|
+ <el-table-column label="监测状态" prop="status" min-width="90" align="center">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-tag :type="monitorStatusTagType(row.status)">{{ monitorStatusText(row.status) }}</el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="液位值" prop="waterLevel" min-width="100" align="center">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <span>{{ row.waterLevel }} m</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="流量值" prop="flowRate" min-width="110" align="center">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <span>{{ row.flowRate }} m³/h</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="水质指标" prop="waterQuality" min-width="90" align="center">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <span :class="'wq-' + row.waterQuality">{{ row.waterQuality }}</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="报警阈值" prop="alarmThreshold" min-width="140" />
|
|
|
+ <el-table-column label="最后更新时间" prop="updateTime" min-width="160" />
|
|
|
+ <el-table-column label="操作" width="280" align="center" fixed="right">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-button link type="primary" @click="handleView(row)">详情</el-button>
|
|
|
+ <el-button link type="primary" @click="handleLocate(row)">定位</el-button>
|
|
|
+ <el-button link type="warning" @click="handleRepair(row)">派单</el-button>
|
|
|
+ <el-button link type="primary" @click="handleEdit(row)">修改</el-button>
|
|
|
+ <el-button link type="danger" @click="handleDelete(row)">删除</el-button>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+
|
|
|
+ <!-- 查看详情弹窗 -->
|
|
|
+ <el-dialog v-model="detailDialogVisible" title="设备实时详情" width="750px">
|
|
|
+ <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.code }}</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">{{ typeText(currentRow.type) }}</span>
|
|
|
+ </div>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <div class="info-item">
|
|
|
+ <span class="info-label">所属监测点:</span>
|
|
|
+ <span class="info-value">{{ currentRow.monitorPoint }}</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">
|
|
|
+ <el-tag :type="monitorStatusTagType(currentRow.status)">{{ monitorStatusText(currentRow.status) }}</el-tag>
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <div class="info-item">
|
|
|
+ <span class="info-label">在线状态:</span>
|
|
|
+ <span class="info-value">
|
|
|
+ <el-tag :type="currentRow.onlineStatus === '1' ? 'success' : 'info'">
|
|
|
+ {{ currentRow.onlineStatus === '1' ? '在线' : '离线' }}
|
|
|
+ </el-tag>
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ <el-row :gutter="20">
|
|
|
+ <el-col :span="12">
|
|
|
+ <div class="info-item">
|
|
|
+ <span class="info-label">液位值:</span>
|
|
|
+ <span class="info-value">{{ currentRow.waterLevel }} m</span>
|
|
|
+ </div>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <div class="info-item">
|
|
|
+ <span class="info-label">流量值:</span>
|
|
|
+ <span class="info-value">{{ currentRow.flowRate }} m³/h</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.waterQuality }}</span>
|
|
|
+ </div>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <div class="info-item">
|
|
|
+ <span class="info-label">报警阈值:</span>
|
|
|
+ <span class="info-value">{{ currentRow.alarmThreshold }}</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.area }}</span>
|
|
|
+ </div>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <div class="info-item">
|
|
|
+ <span class="info-label">最后更新时间:</span>
|
|
|
+ <span class="info-value">{{ currentRow.updateTime }}</span>
|
|
|
+ </div>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ <el-row :gutter="20">
|
|
|
+ <el-col :span="24">
|
|
|
+ <div class="info-item">
|
|
|
+ <span class="info-label">安装位置:</span>
|
|
|
+ <span class="info-value">{{ currentRow.location }}</span>
|
|
|
+ </div>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ </div>
|
|
|
+ </el-dialog>
|
|
|
+
|
|
|
+ <!-- 定位弹窗 -->
|
|
|
+ <el-dialog v-model="locateDialogVisible" title="设备定位" width="700px" @opened="onLocateDialogOpened" :before-close="handleLocateClose">
|
|
|
+ <div class="locate-info">
|
|
|
+ <div class="locate-device">
|
|
|
+ <span class="info-label">设备名称:</span>
|
|
|
+ <span class="info-value">{{ locateRow.name }}</span>
|
|
|
+ <span style="margin-left: 20px" class="info-label">设备编号:</span>
|
|
|
+ <span class="info-value">{{ locateRow.code }}</span>
|
|
|
+ </div>
|
|
|
+ <div id="sbyxLocationMap" class="map-container"></div>
|
|
|
+ <div class="locate-coords">
|
|
|
+ 经度:{{ locateTempLng }} 纬度:{{ locateTempLat }}
|
|
|
+ </div>
|
|
|
+ <div class="locate-tip">点击地图可修改设备定位</div>
|
|
|
+ </div>
|
|
|
+ <template #footer>
|
|
|
+ <el-button @click="handleLocateCancel">取 消</el-button>
|
|
|
+ <el-button type="primary" @click="handleLocateConfirm">确 定</el-button>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+
|
|
|
+ <!-- 维修派单弹窗 -->
|
|
|
+ <el-dialog v-model="repairDialogVisible" title="维修派单" width="600px">
|
|
|
+ <el-form ref="repairFormRef" :model="repairForm" :rules="repairRules" label-width="100px">
|
|
|
+ <el-form-item label="设备名称" prop="name">
|
|
|
+ <el-input v-model="repairForm.name" disabled />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="故障描述" prop="faultDesc">
|
|
|
+ <el-input v-model="repairForm.faultDesc" type="textarea" :rows="3" placeholder="请输入故障描述" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="维修人员" prop="repairPerson">
|
|
|
+ <el-input v-model="repairForm.repairPerson" placeholder="请输入维修人员" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="联系电话" prop="phone">
|
|
|
+ <el-input v-model="repairForm.phone" placeholder="请输入联系电话" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="紧急程度" prop="urgency">
|
|
|
+ <el-select v-model="repairForm.urgency" placeholder="请选择紧急程度" style="width: 100%">
|
|
|
+ <el-option v-for="item in urgencyOptions" :key="item.value" :label="item.label" :value="item.value" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="备注" prop="remark">
|
|
|
+ <el-input v-model="repairForm.remark" type="textarea" :rows="2" placeholder="请输入备注" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ <template #footer>
|
|
|
+ <el-button @click="repairDialogVisible = false">取 消</el-button>
|
|
|
+ <el-button type="primary" @click="handleRepairSubmit">确 定</el-button>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+
|
|
|
+ <!-- 发布预警弹窗 -->
|
|
|
+ <el-dialog v-model="warningDialogVisible" title="发布预警" width="600px">
|
|
|
+ <el-form ref="warningFormRef" :model="warningForm" :rules="warningRules" label-width="100px">
|
|
|
+ <el-form-item label="预警设备" prop="name">
|
|
|
+ <el-input v-model="warningForm.name" disabled />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="预警级别" prop="level">
|
|
|
+ <el-select v-model="warningForm.level" placeholder="请选择预警级别" style="width: 100%">
|
|
|
+ <el-option v-for="item in warningLevelOptions" :key="item.value" :label="item.label" :value="item.value" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="预警内容" prop="content">
|
|
|
+ <el-input v-model="warningForm.content" type="textarea" :rows="3" placeholder="请输入预警内容" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="发布范围" prop="scope">
|
|
|
+ <el-select v-model="warningForm.scope" placeholder="请选择发布范围" style="width: 100%">
|
|
|
+ <el-option v-for="item in scopeOptions" :key="item.value" :label="item.label" :value="item.value" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ <template #footer>
|
|
|
+ <el-button @click="warningDialogVisible = false">取 消</el-button>
|
|
|
+ <el-button type="primary" @click="handleWarningSubmit">确 定</el-button>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+
|
|
|
+ <!-- 新增/修改设备弹窗 -->
|
|
|
+ <el-dialog v-model="deviceDialogVisible" :title="deviceDialogTitle" width="700px" @close="resetDeviceForm">
|
|
|
+ <el-form ref="deviceFormRef" :model="deviceForm" :rules="deviceFormRules" label-width="100px">
|
|
|
+ <el-row :gutter="20">
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="设备名称" prop="name">
|
|
|
+ <el-input v-model="deviceForm.name" placeholder="请输入设备名称" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="设备编号" prop="code">
|
|
|
+ <el-input v-model="deviceForm.code" placeholder="请输入设备编号" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ <el-row :gutter="20">
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="设备类型" prop="type">
|
|
|
+ <el-select v-model="deviceForm.type" placeholder="请选择设备类型" style="width: 100%">
|
|
|
+ <el-option v-for="item in typeOptions" :key="item.value" :label="item.label" :value="item.value" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="所属监测点" prop="monitorPoint">
|
|
|
+ <el-input v-model="deviceForm.monitorPoint" placeholder="请输入所属监测点" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ <el-row :gutter="20">
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="安装位置" prop="location">
|
|
|
+ <el-input v-model="deviceForm.location" placeholder="请输入安装位置" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="设备状态" prop="status">
|
|
|
+ <el-select v-model="deviceForm.status" placeholder="请选择设备状态" style="width: 100%">
|
|
|
+ <el-option v-for="item in deviceStatusOptions" :key="item.value" :label="item.label" :value="item.value" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ <el-row :gutter="20">
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="监测状态" prop="monitorStatus">
|
|
|
+ <el-select v-model="deviceForm.monitorStatus" placeholder="请选择监测状态" style="width: 100%">
|
|
|
+ <el-option v-for="item in monitorStatusOptions" :key="item.value" :label="item.label" :value="item.value" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="液位值" prop="waterLevel">
|
|
|
+ <el-input v-model="deviceForm.waterLevel" placeholder="请输入液位值" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ <el-row :gutter="20">
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="流量值" prop="flowRate">
|
|
|
+ <el-input v-model="deviceForm.flowRate" placeholder="请输入流量值" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="水质指标" prop="waterQuality">
|
|
|
+ <el-select v-model="deviceForm.waterQuality" placeholder="请选择水质指标" style="width: 100%">
|
|
|
+ <el-option v-for="item in waterQualityOptions" :key="item.value" :label="item.label" :value="item.value" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ <el-row :gutter="20">
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="报警阈值" prop="alarmThreshold">
|
|
|
+ <el-input v-model="deviceForm.alarmThreshold" placeholder="请输入报警阈值" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="安装时间" prop="installTime">
|
|
|
+ <el-date-picker v-model="deviceForm.installTime" type="date" placeholder="请选择安装时间" value-format="YYYY-MM-DD" style="width: 100%" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ <el-row :gutter="20">
|
|
|
+ <el-col :span="24">
|
|
|
+ <el-form-item label="备注" prop="remark">
|
|
|
+ <el-input v-model="deviceForm.remark" type="textarea" :rows="3" placeholder="请输入备注" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ </el-form>
|
|
|
+ <template #footer>
|
|
|
+ <el-button @click="deviceDialogVisible = false">取 消</el-button>
|
|
|
+ <el-button type="primary" @click="handleDeviceSubmit">确 定</el-button>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup name="Sbyx">
|
|
|
+import { ref, computed } from 'vue'
|
|
|
+import { ElMessage, ElMessageBox } from 'element-plus'
|
|
|
+import locationIcon from '@/assets/images/location.png'
|
|
|
+
|
|
|
+// 地图标记创建(用Label+img代替Marker)
|
|
|
+function createMapMarker(map, bPoint, name) {
|
|
|
+ const html = `<div style="text-align:center;cursor:pointer;">
|
|
|
+ <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>
|
|
|
+ <div><img src="${locationIcon}" style="width:28px;height:28px;display:block;margin:0 auto;"/></div>
|
|
|
+ </div>`
|
|
|
+ const label = new BMapGL.Label(html, {
|
|
|
+ position: bPoint,
|
|
|
+ offset: new BMapGL.Size(-30, -50)
|
|
|
+ })
|
|
|
+ label.setStyle({
|
|
|
+ border: 'none',
|
|
|
+ background: 'transparent',
|
|
|
+ padding: '0'
|
|
|
+ })
|
|
|
+ map.addOverlay(label)
|
|
|
+ return label
|
|
|
+}
|
|
|
+
|
|
|
+// ==================== 选项配置 ====================
|
|
|
+const typeOptions = [
|
|
|
+ { label: '液位计', value: '1' },
|
|
|
+ { label: '流量计', value: '2' },
|
|
|
+ { label: '水质监测仪', value: '3' },
|
|
|
+ { label: '雨量计', value: '4' }
|
|
|
+]
|
|
|
+
|
|
|
+const monitorStatusOptions = [
|
|
|
+ { label: '正常', value: '1' },
|
|
|
+ { label: '预警', value: '2' },
|
|
|
+ { label: '报警', value: '3' }
|
|
|
+]
|
|
|
+
|
|
|
+const areaOptions = [
|
|
|
+ { label: '城北片区', value: '1' },
|
|
|
+ { label: '河西片区', value: '2' },
|
|
|
+ { label: '南区片区', value: '3' },
|
|
|
+ { label: '东区片区', value: '4' },
|
|
|
+ { label: '中心片区', value: '5' },
|
|
|
+ { label: '西区片区', value: '6' }
|
|
|
+]
|
|
|
+
|
|
|
+const urgencyOptions = [
|
|
|
+ { label: '一般', value: '1' },
|
|
|
+ { label: '紧急', value: '2' },
|
|
|
+ { label: '非常紧急', value: '3' }
|
|
|
+]
|
|
|
+
|
|
|
+const warningLevelOptions = [
|
|
|
+ { label: '一级(红色)', value: '1' },
|
|
|
+ { label: '二级(橙色)', value: '2' },
|
|
|
+ { label: '三级(黄色)', value: '3' },
|
|
|
+ { label: '四级(蓝色)', value: '4' }
|
|
|
+]
|
|
|
+
|
|
|
+const scopeOptions = [
|
|
|
+ { label: '全部', value: '1' },
|
|
|
+ { label: '相关部门', value: '2' },
|
|
|
+ { label: '指定人员', value: '3' }
|
|
|
+]
|
|
|
+
|
|
|
+const deviceStatusOptions = [
|
|
|
+ { label: '在线', value: '1' },
|
|
|
+ { label: '离线', value: '2' },
|
|
|
+ { label: '故障', value: '3' },
|
|
|
+ { label: '维修中', value: '4' }
|
|
|
+]
|
|
|
+
|
|
|
+const waterQualityOptions = [
|
|
|
+ { label: '优', value: '优' },
|
|
|
+ { label: '良', value: '良' },
|
|
|
+ { label: '中', value: '中' },
|
|
|
+ { label: '差', value: '差' }
|
|
|
+]
|
|
|
+
|
|
|
+function typeText(val) {
|
|
|
+ const map = { '1': '液位计', '2': '流量计', '3': '水质监测仪', '4': '雨量计' }
|
|
|
+ return map[val] || '-'
|
|
|
+}
|
|
|
+
|
|
|
+function monitorStatusText(val) {
|
|
|
+ const map = { '1': '正常', '2': '预警', '3': '报警' }
|
|
|
+ return map[val] || '-'
|
|
|
+}
|
|
|
+
|
|
|
+function monitorStatusTagType(val) {
|
|
|
+ const map = { '1': 'success', '2': 'warning', '3': 'danger' }
|
|
|
+ return map[val] || 'info'
|
|
|
+}
|
|
|
+
|
|
|
+// ==================== 模拟数据 ====================
|
|
|
+const tableData = ref([
|
|
|
+ {
|
|
|
+ id: 1, name: '城北液位监测仪', code: 'YWL-2025-001', type: '1',
|
|
|
+ monitorPoint: '城北排水监测站', area: '1', location: '城北路段排水井A3号',
|
|
|
+ status: '1', onlineStatus: '1',
|
|
|
+ waterLevel: 1.82, flowRate: 120, waterQuality: '良',
|
|
|
+ alarmThreshold: '液位>3.0m', updateTime: '2026-06-11 08:32:15',
|
|
|
+ lng: '110.393', lat: '28.452'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 2, name: '河西流量计', code: 'LXJ-2025-002', type: '2',
|
|
|
+ monitorPoint: '河西排水监测站', area: '2', location: '河西大道排水管道B7号',
|
|
|
+ status: '3', onlineStatus: '1',
|
|
|
+ waterLevel: 2.65, flowRate: 435, waterQuality: '中',
|
|
|
+ alarmThreshold: '流量>400m³/h', updateTime: '2026-06-11 08:30:42',
|
|
|
+ lng: '110.388', lat: '28.456'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 3, name: '南区水质监测仪', code: 'SZJ-2025-003', type: '3',
|
|
|
+ monitorPoint: '南区水质监测站', area: '3', location: '南环路污水处理厂入口',
|
|
|
+ status: '2', onlineStatus: '1',
|
|
|
+ waterLevel: 1.45, flowRate: 185, waterQuality: '差',
|
|
|
+ alarmThreshold: '水质>差', updateTime: '2026-06-11 08:31:08',
|
|
|
+ lng: '110.402', lat: '28.448'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 4, name: '东区雨量计', code: 'YLJ-2025-004', type: '4',
|
|
|
+ monitorPoint: '东区气象监测站', area: '4', location: '东环路市政大楼楼顶',
|
|
|
+ status: '1', onlineStatus: '1',
|
|
|
+ waterLevel: 0.82, flowRate: 65, waterQuality: '优',
|
|
|
+ alarmThreshold: '液位>2.5m', updateTime: '2026-06-11 08:29:55',
|
|
|
+ lng: '110.408', lat: '28.454'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 5, name: '中心区液位监测仪', code: 'YWL-2025-005', type: '1',
|
|
|
+ monitorPoint: '中心区排水监测站', area: '5', location: '人民路地下管网C2号',
|
|
|
+ status: '3', onlineStatus: '1',
|
|
|
+ waterLevel: 3.25, flowRate: 380, waterQuality: '中',
|
|
|
+ alarmThreshold: '液位>3.0m', updateTime: '2026-06-11 08:33:01',
|
|
|
+ lng: '110.396', lat: '28.451'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 6, name: '北区流量计', code: 'LXJ-2025-006', type: '2',
|
|
|
+ monitorPoint: '北区排水监测站', area: '1', location: '北环路排水泵站出口',
|
|
|
+ status: '1', onlineStatus: '1',
|
|
|
+ waterLevel: 1.15, flowRate: 210, waterQuality: '良',
|
|
|
+ alarmThreshold: '流量>350m³/h', updateTime: '2026-06-11 08:28:47',
|
|
|
+ lng: '110.401', lat: '28.460'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 7, name: '西区水质监测仪', code: 'SZJ-2025-007', type: '3',
|
|
|
+ monitorPoint: '西区水质监测站', area: '6', location: '西环路河道排放口',
|
|
|
+ status: '1', onlineStatus: '2',
|
|
|
+ waterLevel: 0.95, flowRate: 0, waterQuality: '-',
|
|
|
+ alarmThreshold: '水质>差', updateTime: '2026-06-10 17:22:30',
|
|
|
+ lng: '110.385', lat: '28.449'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 8, name: '开发区雨量计', code: 'YLJ-2025-008', type: '4',
|
|
|
+ monitorPoint: '开发区气象监测站', area: '4', location: '开发区管委会大楼楼顶',
|
|
|
+ status: '1', onlineStatus: '1',
|
|
|
+ waterLevel: 0.55, flowRate: 52, waterQuality: '优',
|
|
|
+ alarmThreshold: '液位>2.0m', updateTime: '2026-06-11 08:31:55',
|
|
|
+ lng: '110.410', lat: '28.458'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 9, name: '城北二号液位计', code: 'YWL-2025-009', type: '1',
|
|
|
+ monitorPoint: '城北排水监测站', area: '1', location: '城北路段排水井A7号',
|
|
|
+ status: '2', onlineStatus: '1',
|
|
|
+ waterLevel: 2.78, flowRate: 295, waterQuality: '良',
|
|
|
+ alarmThreshold: '液位>3.0m', updateTime: '2026-06-11 08:32:40',
|
|
|
+ lng: '110.394', lat: '28.456'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 10, name: '南区流量计', code: 'LXJ-2025-010', type: '2',
|
|
|
+ monitorPoint: '南区排水监测站', area: '3', location: '南环路排水管道D3号',
|
|
|
+ status: '1', onlineStatus: '2',
|
|
|
+ waterLevel: 1.38, flowRate: 0, waterQuality: '-',
|
|
|
+ alarmThreshold: '流量>300m³/h', updateTime: '2026-06-10 19:45:12',
|
|
|
+ lng: '110.398', lat: '28.444'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 11, name: '中心区水质监测仪', code: 'SZJ-2025-011', type: '3',
|
|
|
+ monitorPoint: '中心区水质监测站', area: '5', location: '人民路河道排放口',
|
|
|
+ status: '1', onlineStatus: '1',
|
|
|
+ waterLevel: 1.05, flowRate: 155, waterQuality: '优',
|
|
|
+ alarmThreshold: '水质>差', updateTime: '2026-06-11 08:30:22',
|
|
|
+ lng: '110.395', lat: '28.453'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 12, name: '河西液位监测仪', code: 'YWL-2025-012', type: '1',
|
|
|
+ monitorPoint: '河西排水监测站', area: '2', location: '河西大道排水井E5号',
|
|
|
+ status: '3', onlineStatus: '1',
|
|
|
+ waterLevel: 3.42, flowRate: 468, waterQuality: '差',
|
|
|
+ alarmThreshold: '液位>3.0m', updateTime: '2026-06-11 08:33:18',
|
|
|
+ lng: '110.389', lat: '28.455'
|
|
|
+ }
|
|
|
+])
|
|
|
+
|
|
|
+// ==================== 统计数据 ====================
|
|
|
+const stats = computed(() => {
|
|
|
+ const total = tableData.value.length
|
|
|
+ const online = tableData.value.filter(d => d.onlineStatus === '1').length
|
|
|
+ const offline = tableData.value.filter(d => d.onlineStatus === '2').length
|
|
|
+ const alarm = tableData.value.filter(d => d.status === '3').length
|
|
|
+ const onlineRate = total > 0 ? ((online / total) * 100).toFixed(1) : 0
|
|
|
+ return { total, online, offline, alarm, onlineRate }
|
|
|
+})
|
|
|
+
|
|
|
+// ==================== 搜索参数 ====================
|
|
|
+const showMoreSearch = ref(false)
|
|
|
+const queryParams = ref({
|
|
|
+ name: '',
|
|
|
+ type: '',
|
|
|
+ status: '',
|
|
|
+ area: ''
|
|
|
+})
|
|
|
+
|
|
|
+// ==================== 过滤列表 ====================
|
|
|
+const filteredList = computed(() => {
|
|
|
+ return tableData.value.filter(item => {
|
|
|
+ const nameMatch = !queryParams.value.name || item.name.includes(queryParams.value.name)
|
|
|
+ const typeMatch = !queryParams.value.type || item.type === queryParams.value.type
|
|
|
+ const statusMatch = !queryParams.value.status || item.status === queryParams.value.status
|
|
|
+ const areaMatch = !queryParams.value.area || item.area === queryParams.value.area
|
|
|
+ return nameMatch && typeMatch && statusMatch && areaMatch
|
|
|
+ })
|
|
|
+})
|
|
|
+
|
|
|
+function handleQuery() {
|
|
|
+ // 触发 computed 重新计算
|
|
|
+ queryParams.value = { ...queryParams.value }
|
|
|
+}
|
|
|
+
|
|
|
+function resetQuery() {
|
|
|
+ queryParams.value = { name: '', type: '', status: '', area: '' }
|
|
|
+}
|
|
|
+
|
|
|
+// ==================== 多选 ====================
|
|
|
+const selectedRows = ref([])
|
|
|
+function handleSelectionChange(selection) {
|
|
|
+ selectedRows.value = selection
|
|
|
+}
|
|
|
+
|
|
|
+// ==================== 导出 ====================
|
|
|
+function handleExport() {
|
|
|
+ ElMessage.success('导出成功(模拟)')
|
|
|
+}
|
|
|
+
|
|
|
+// ==================== 查看详情弹窗 ====================
|
|
|
+const detailDialogVisible = ref(false)
|
|
|
+const currentRow = ref({})
|
|
|
+
|
|
|
+function handleView(row) {
|
|
|
+ currentRow.value = { ...row }
|
|
|
+ detailDialogVisible.value = true
|
|
|
+}
|
|
|
+
|
|
|
+// ==================== 定位弹窗 ====================
|
|
|
+const locateDialogVisible = ref(false)
|
|
|
+const locateRow = ref({})
|
|
|
+let locateMapInstance = null
|
|
|
+let locateCurrentLabel = null
|
|
|
+const locateTempLng = ref('')
|
|
|
+const locateTempLat = ref('')
|
|
|
+
|
|
|
+function handleLocate(row) {
|
|
|
+ locateRow.value = { ...row }
|
|
|
+ locateTempLng.value = row.lng || ''
|
|
|
+ locateTempLat.value = row.lat || ''
|
|
|
+ locateDialogVisible.value = true
|
|
|
+}
|
|
|
+
|
|
|
+function onLocateDialogOpened() {
|
|
|
+ setTimeout(() => {
|
|
|
+ initLocateMap()
|
|
|
+ }, 300)
|
|
|
+}
|
|
|
+
|
|
|
+function initLocateMap() {
|
|
|
+ const container = document.getElementById('sbyxLocationMap')
|
|
|
+ if (!container) return
|
|
|
+
|
|
|
+ try {
|
|
|
+ locateMapInstance = new BMapGL.Map('sbyxLocationMap')
|
|
|
+ const lng = parseFloat(locateRow.value.lng)
|
|
|
+ const lat = parseFloat(locateRow.value.lat)
|
|
|
+ const centerPoint = (lng && lat)
|
|
|
+ ? new BMapGL.Point(lng, lat)
|
|
|
+ : new BMapGL.Point(110.393, 28.452)
|
|
|
+ locateMapInstance.centerAndZoom(centerPoint, 15)
|
|
|
+ locateMapInstance.enableScrollWheelZoom(true)
|
|
|
+
|
|
|
+ // 如果有坐标,显示标记
|
|
|
+ if (lng && lat) {
|
|
|
+ const bPoint = new BMapGL.Point(lng, lat)
|
|
|
+ locateCurrentLabel = createMapMarker(locateMapInstance, bPoint, locateRow.value.name)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 点击地图修改定位
|
|
|
+ locateMapInstance.addEventListener('click', function (e) {
|
|
|
+ const clickLng = e.latlng.lng
|
|
|
+ const clickLat = e.latlng.lat
|
|
|
+ locateTempLng.value = clickLng.toFixed(6)
|
|
|
+ locateTempLat.value = clickLat.toFixed(6)
|
|
|
+
|
|
|
+ // 清除旧标记,添加新标记
|
|
|
+ if (locateCurrentLabel) {
|
|
|
+ locateMapInstance.removeOverlay(locateCurrentLabel)
|
|
|
+ }
|
|
|
+ const bPoint = new BMapGL.Point(clickLng, clickLat)
|
|
|
+ locateCurrentLabel = createMapMarker(locateMapInstance, bPoint, locateRow.value.name)
|
|
|
+ })
|
|
|
+ } catch (e) {
|
|
|
+ console.error('百度地图初始化失败:', e)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function handleLocateClose(done) {
|
|
|
+ if (locateMapInstance) {
|
|
|
+ locateMapInstance = null
|
|
|
+ }
|
|
|
+ locateCurrentLabel = null
|
|
|
+ done()
|
|
|
+}
|
|
|
+
|
|
|
+function handleLocateCancel() {
|
|
|
+ if (locateMapInstance) {
|
|
|
+ locateMapInstance = null
|
|
|
+ }
|
|
|
+ locateCurrentLabel = null
|
|
|
+ locateDialogVisible.value = false
|
|
|
+}
|
|
|
+
|
|
|
+function handleLocateConfirm() {
|
|
|
+ // 将临时坐标保存回设备数据
|
|
|
+ const index = tableData.value.findIndex(item => item.id === locateRow.value.id)
|
|
|
+ if (index !== -1) {
|
|
|
+ tableData.value[index].lng = locateTempLng.value
|
|
|
+ tableData.value[index].lat = locateTempLat.value
|
|
|
+ }
|
|
|
+ if (locateMapInstance) {
|
|
|
+ locateMapInstance = null
|
|
|
+ }
|
|
|
+ locateCurrentLabel = null
|
|
|
+ locateDialogVisible.value = false
|
|
|
+ ElMessage.success('设备定位更新成功')
|
|
|
+}
|
|
|
+
|
|
|
+// ==================== 维修派单弹窗 ====================
|
|
|
+const repairDialogVisible = ref(false)
|
|
|
+const repairFormRef = ref(null)
|
|
|
+const repairForm = ref({
|
|
|
+ name: '',
|
|
|
+ faultDesc: '',
|
|
|
+ repairPerson: '',
|
|
|
+ phone: '',
|
|
|
+ urgency: '',
|
|
|
+ remark: ''
|
|
|
+})
|
|
|
+
|
|
|
+const repairRules = {
|
|
|
+ faultDesc: [{ required: true, message: '请输入故障描述', trigger: 'blur' }],
|
|
|
+ repairPerson: [{ required: true, message: '请输入维修人员', trigger: 'blur' }],
|
|
|
+ phone: [
|
|
|
+ { required: true, message: '请输入联系电话', trigger: 'blur' },
|
|
|
+ { pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' }
|
|
|
+ ],
|
|
|
+ urgency: [{ required: true, message: '请选择紧急程度', trigger: 'change' }]
|
|
|
+}
|
|
|
+
|
|
|
+function openRepairDialog(name) {
|
|
|
+ repairForm.value = {
|
|
|
+ name: name,
|
|
|
+ faultDesc: '',
|
|
|
+ repairPerson: '',
|
|
|
+ phone: '',
|
|
|
+ urgency: '',
|
|
|
+ remark: ''
|
|
|
+ }
|
|
|
+ repairDialogVisible.value = true
|
|
|
+}
|
|
|
+
|
|
|
+function handleRepair(row) {
|
|
|
+ openRepairDialog(row.name)
|
|
|
+}
|
|
|
+
|
|
|
+function handleBatchRepair() {
|
|
|
+ const names = selectedRows.value.map(r => r.name).join('、')
|
|
|
+ openRepairDialog(names)
|
|
|
+}
|
|
|
+
|
|
|
+function handleRepairSubmit() {
|
|
|
+ repairFormRef.value.validate(valid => {
|
|
|
+ if (!valid) return
|
|
|
+ ElMessage.success('维修派单成功')
|
|
|
+ repairDialogVisible.value = false
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+// ==================== 发布预警弹窗 ====================
|
|
|
+const warningDialogVisible = ref(false)
|
|
|
+const warningFormRef = ref(null)
|
|
|
+const warningForm = ref({
|
|
|
+ name: '',
|
|
|
+ level: '',
|
|
|
+ content: '',
|
|
|
+ scope: ''
|
|
|
+})
|
|
|
+
|
|
|
+const warningRules = {
|
|
|
+ level: [{ required: true, message: '请选择预警级别', trigger: 'change' }],
|
|
|
+ content: [{ required: true, message: '请输入预警内容', trigger: 'blur' }],
|
|
|
+ scope: [{ required: true, message: '请选择发布范围', trigger: 'change' }]
|
|
|
+}
|
|
|
+
|
|
|
+function openWarningDialog(name) {
|
|
|
+ warningForm.value = {
|
|
|
+ name: name,
|
|
|
+ level: '',
|
|
|
+ content: '',
|
|
|
+ scope: ''
|
|
|
+ }
|
|
|
+ warningDialogVisible.value = true
|
|
|
+}
|
|
|
+
|
|
|
+function handleBatchWarning() {
|
|
|
+ const names = selectedRows.value.map(r => r.name).join('、')
|
|
|
+ openWarningDialog(names)
|
|
|
+}
|
|
|
+
|
|
|
+function handleWarningSubmit() {
|
|
|
+ warningFormRef.value.validate(valid => {
|
|
|
+ if (!valid) return
|
|
|
+ ElMessage.success('预警发布成功')
|
|
|
+ warningDialogVisible.value = false
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+// ==================== 新增/修改设备弹窗 ====================
|
|
|
+const deviceDialogVisible = ref(false)
|
|
|
+const deviceDialogTitle = ref('新增设备')
|
|
|
+const deviceFormRef = ref(null)
|
|
|
+const deviceFormMode = ref('add') // add | edit
|
|
|
+const deviceForm = ref({
|
|
|
+ name: '',
|
|
|
+ code: '',
|
|
|
+ type: '',
|
|
|
+ monitorPoint: '',
|
|
|
+ location: '',
|
|
|
+ status: '',
|
|
|
+ monitorStatus: '',
|
|
|
+ waterLevel: '',
|
|
|
+ flowRate: '',
|
|
|
+ waterQuality: '',
|
|
|
+ alarmThreshold: '',
|
|
|
+ installTime: '',
|
|
|
+ remark: ''
|
|
|
+})
|
|
|
+
|
|
|
+const deviceFormRules = {
|
|
|
+ name: [{ required: true, message: '请输入设备名称', trigger: 'blur' }],
|
|
|
+ code: [{ required: true, message: '请输入设备编号', trigger: 'blur' }],
|
|
|
+ type: [{ required: true, message: '请选择设备类型', trigger: 'change' }],
|
|
|
+ monitorPoint: [{ required: true, message: '请输入所属监测点', trigger: 'blur' }],
|
|
|
+ location: [{ required: true, message: '请输入安装位置', trigger: 'blur' }],
|
|
|
+ status: [{ required: true, message: '请选择设备状态', trigger: 'change' }]
|
|
|
+}
|
|
|
+
|
|
|
+function resetDeviceForm() {
|
|
|
+ deviceForm.value = {
|
|
|
+ name: '',
|
|
|
+ code: '',
|
|
|
+ type: '',
|
|
|
+ monitorPoint: '',
|
|
|
+ location: '',
|
|
|
+ status: '',
|
|
|
+ monitorStatus: '',
|
|
|
+ waterLevel: '',
|
|
|
+ flowRate: '',
|
|
|
+ waterQuality: '',
|
|
|
+ alarmThreshold: '',
|
|
|
+ installTime: '',
|
|
|
+ remark: ''
|
|
|
+ }
|
|
|
+ if (deviceFormRef.value) {
|
|
|
+ deviceFormRef.value.resetFields()
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function handleAdd() {
|
|
|
+ deviceFormMode.value = 'add'
|
|
|
+ deviceDialogTitle.value = '新增设备'
|
|
|
+ resetDeviceForm()
|
|
|
+ deviceDialogVisible.value = true
|
|
|
+}
|
|
|
+
|
|
|
+function handleEdit(row) {
|
|
|
+ deviceFormMode.value = 'edit'
|
|
|
+ deviceDialogTitle.value = '修改设备'
|
|
|
+ deviceForm.value = {
|
|
|
+ name: row.name,
|
|
|
+ code: row.code,
|
|
|
+ type: row.type,
|
|
|
+ monitorPoint: row.monitorPoint,
|
|
|
+ location: row.location,
|
|
|
+ status: row.onlineStatus || '',
|
|
|
+ monitorStatus: row.status || '',
|
|
|
+ waterLevel: row.waterLevel ?? '',
|
|
|
+ flowRate: row.flowRate ?? '',
|
|
|
+ waterQuality: row.waterQuality || '',
|
|
|
+ alarmThreshold: row.alarmThreshold || '',
|
|
|
+ installTime: row.installTime || '',
|
|
|
+ remark: row.remark || ''
|
|
|
+ }
|
|
|
+ deviceForm.value._editId = row.id
|
|
|
+ deviceDialogVisible.value = true
|
|
|
+}
|
|
|
+
|
|
|
+function handleDeviceSubmit() {
|
|
|
+ deviceFormRef.value.validate(valid => {
|
|
|
+ if (!valid) return
|
|
|
+ const form = { ...deviceForm.value }
|
|
|
+ const now = new Date()
|
|
|
+ const pad = n => String(n).padStart(2, '0')
|
|
|
+ const currentTime = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())} ${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`
|
|
|
+
|
|
|
+ if (deviceFormMode.value === 'add') {
|
|
|
+ const maxId = tableData.value.reduce((max, item) => Math.max(max, item.id), 0)
|
|
|
+ const newItem = {
|
|
|
+ id: maxId + 1,
|
|
|
+ name: form.name,
|
|
|
+ code: form.code,
|
|
|
+ type: form.type,
|
|
|
+ monitorPoint: form.monitorPoint,
|
|
|
+ area: '',
|
|
|
+ location: form.location,
|
|
|
+ status: form.monitorStatus || '1',
|
|
|
+ onlineStatus: form.status || '1',
|
|
|
+ waterLevel: form.waterLevel !== '' ? Number(form.waterLevel) : 0,
|
|
|
+ flowRate: form.flowRate !== '' ? Number(form.flowRate) : 0,
|
|
|
+ waterQuality: form.waterQuality || '-',
|
|
|
+ alarmThreshold: form.alarmThreshold || '',
|
|
|
+ updateTime: currentTime,
|
|
|
+ lng: '',
|
|
|
+ lat: '',
|
|
|
+ installTime: form.installTime || '',
|
|
|
+ remark: form.remark || ''
|
|
|
+ }
|
|
|
+ tableData.value.push(newItem)
|
|
|
+ ElMessage.success('新增设备成功')
|
|
|
+ } else {
|
|
|
+ const index = tableData.value.findIndex(item => item.id === form._editId)
|
|
|
+ if (index !== -1) {
|
|
|
+ tableData.value[index] = {
|
|
|
+ ...tableData.value[index],
|
|
|
+ name: form.name,
|
|
|
+ code: form.code,
|
|
|
+ type: form.type,
|
|
|
+ monitorPoint: form.monitorPoint,
|
|
|
+ location: form.location,
|
|
|
+ status: form.monitorStatus || tableData.value[index].status,
|
|
|
+ onlineStatus: form.status || tableData.value[index].onlineStatus,
|
|
|
+ waterLevel: form.waterLevel !== '' ? Number(form.waterLevel) : tableData.value[index].waterLevel,
|
|
|
+ flowRate: form.flowRate !== '' ? Number(form.flowRate) : tableData.value[index].flowRate,
|
|
|
+ waterQuality: form.waterQuality || tableData.value[index].waterQuality,
|
|
|
+ alarmThreshold: form.alarmThreshold || tableData.value[index].alarmThreshold,
|
|
|
+ installTime: form.installTime || tableData.value[index].installTime,
|
|
|
+ remark: form.remark || tableData.value[index].remark,
|
|
|
+ updateTime: currentTime
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ElMessage.success('修改设备成功')
|
|
|
+ }
|
|
|
+ deviceDialogVisible.value = false
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+// ==================== 删除设备 ====================
|
|
|
+function handleDelete(row) {
|
|
|
+ ElMessageBox.confirm(
|
|
|
+ `确认删除设备「${row.name}」吗?`,
|
|
|
+ '提示',
|
|
|
+ { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }
|
|
|
+ ).then(() => {
|
|
|
+ const index = tableData.value.findIndex(item => item.id === row.id)
|
|
|
+ if (index !== -1) {
|
|
|
+ tableData.value.splice(index, 1)
|
|
|
+ }
|
|
|
+ ElMessage.success('删除成功')
|
|
|
+ }).catch(() => {})
|
|
|
+}
|
|
|
+
|
|
|
+function handleBatchDelete() {
|
|
|
+ const names = selectedRows.value.map(r => r.name).join('、')
|
|
|
+ ElMessageBox.confirm(
|
|
|
+ `确认删除以下设备吗?${names}`,
|
|
|
+ '提示',
|
|
|
+ { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }
|
|
|
+ ).then(() => {
|
|
|
+ const ids = selectedRows.value.map(r => r.id)
|
|
|
+ tableData.value = tableData.value.filter(item => !ids.includes(item.id))
|
|
|
+ ElMessage.success('批量删除成功')
|
|
|
+ }).catch(() => {})
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.app-container {
|
|
|
+ padding: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 统计卡片 */
|
|
|
+.stat-cards {
|
|
|
+ display: flex;
|
|
|
+ gap: 16px;
|
|
|
+ margin-bottom: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-card {
|
|
|
+ flex: 1;
|
|
|
+ min-width: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-card :deep(.el-card__body) {
|
|
|
+ padding: 16px 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-card-inner {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 16px;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-icon {
|
|
|
+ width: 60px;
|
|
|
+ height: 60px;
|
|
|
+ border-radius: 12px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-blue .stat-icon {
|
|
|
+ background: rgba(64, 158, 255, 0.1);
|
|
|
+ color: #409EFF;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-green .stat-icon {
|
|
|
+ background: rgba(103, 194, 58, 0.1);
|
|
|
+ color: #67C23A;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-gray .stat-icon {
|
|
|
+ background: rgba(144, 147, 153, 0.1);
|
|
|
+ color: #909399;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-red .stat-icon {
|
|
|
+ background: rgba(245, 108, 108, 0.1);
|
|
|
+ color: #F56C6C;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-cyan .stat-icon {
|
|
|
+ background: rgba(0, 210, 219, 0.1);
|
|
|
+ color: #00D2DB;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-info {
|
|
|
+ flex: 1;
|
|
|
+ min-width: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-value {
|
|
|
+ font-size: 28px;
|
|
|
+ font-weight: 700;
|
|
|
+ line-height: 1.2;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-blue .stat-value {
|
|
|
+ color: #409EFF;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-green .stat-value {
|
|
|
+ color: #67C23A;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-gray .stat-value {
|
|
|
+ color: #909399;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-red .stat-value {
|
|
|
+ color: #F56C6C;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-cyan .stat-value {
|
|
|
+ color: #00D2DB;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-label {
|
|
|
+ font-size: 14px;
|
|
|
+ color: #909399;
|
|
|
+ margin-top: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 搜索表单 */
|
|
|
+.search-form {
|
|
|
+ margin-bottom: 16px;
|
|
|
+}
|
|
|
+
|
|
|
+.expand-icon {
|
|
|
+ transition: transform 0.3s;
|
|
|
+ margin-left: 2px;
|
|
|
+}
|
|
|
+
|
|
|
+.expand-icon.is-rotate {
|
|
|
+ transform: rotate(180deg);
|
|
|
+}
|
|
|
+
|
|
|
+/* 操作按钮 */
|
|
|
+.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;
|
|
|
+}
|
|
|
+
|
|
|
+/* 定位弹窗 */
|
|
|
+.locate-info {
|
|
|
+ text-align: center;
|
|
|
+}
|
|
|
+
|
|
|
+.locate-device {
|
|
|
+ text-align: left;
|
|
|
+ margin-bottom: 16px;
|
|
|
+}
|
|
|
+
|
|
|
+.map-container {
|
|
|
+ width: 100%;
|
|
|
+ height: 350px;
|
|
|
+ border-radius: 8px;
|
|
|
+ overflow: hidden;
|
|
|
+ border: 1px solid #dcdfe6;
|
|
|
+}
|
|
|
+
|
|
|
+.locate-coords {
|
|
|
+ margin-top: 12px;
|
|
|
+ font-size: 14px;
|
|
|
+ color: #303133;
|
|
|
+}
|
|
|
+
|
|
|
+.locate-tip {
|
|
|
+ margin-top: 6px;
|
|
|
+ font-size: 12px;
|
|
|
+ color: #909399;
|
|
|
+}
|
|
|
+
|
|
|
+/* 水质指标颜色 */
|
|
|
+.wq-优 {
|
|
|
+ color: #67C23A;
|
|
|
+ font-weight: 600;
|
|
|
+}
|
|
|
+
|
|
|
+.wq-良 {
|
|
|
+ color: #409EFF;
|
|
|
+ font-weight: 600;
|
|
|
+}
|
|
|
+
|
|
|
+.wq-中 {
|
|
|
+ color: #E6A23C;
|
|
|
+ font-weight: 600;
|
|
|
+}
|
|
|
+
|
|
|
+.wq-差 {
|
|
|
+ color: #F56C6C;
|
|
|
+ font-weight: 600;
|
|
|
+}
|
|
|
+</style>
|