Procházet zdrojové kódy

报警阈值统一管理

zengy před 4 týdny
rodič
revize
46508314a8

+ 1 - 0
memory/index.md

@@ -63,3 +63,4 @@ npm run dev       # 默认 http://localhost:80
 - [2026-09-09:water_pipe_info 收尾迁移(E 顺序执行完成,旧表链路全删)](sessions/2026-09-09.md)
 - [2026-09-10:WGS84→BD09 诊断与管理端工单生命周期共识更新](sessions/2026-09-10.md)
 - [2026-09-16:监测报警 GIS 接入统一画布(Ticket #4)](sessions/2026-09-16.md)
+- [2026-09-22:报警阈值移除备注字段](sessions/2026-09-22.md)

+ 46 - 0
memory/sessions/2026-09-21.md

@@ -0,0 +1,46 @@
+# 2026-09-21:统一设备报警阈值配置
+
+## 目标
+
+- 新增统一报警阈值配置,按设备编码所属设备类型读取 `equipment_warning_type` 指标元数据。
+- 支持 data_type 0(最小/最大)、1(仅最小)、2(仅最大),支持设备类型批量保存。
+- 供水、排水、燃气、窨井旧阈值入口迁移到生命线统一页面,并按根设备类型隔离。
+
+## 实现
+
+- 后端新增 `base/warning` 下设备指标元数据、阈值视图、保存请求、Mapper 和服务;`zksy-admin` 新增 `/warning/threshold/types|devices|save`。
+- 保存按设备编码 + warning_code 幂等更新已有 `warning_threshold`,并校验设备范围、指标归属和上下限关系。
+- 前端新增 `src/views/subSystem/lifeCompany/equipmentWarningType/index.vue` 与 API;旧供水/排水/燃气阈值页面改为统一页面兼容入口。
+- 新增 `migration_equipment_warning_threshold_config.sql`,创建 `equipment_warning_type` 表、新菜单,并迁移旧菜单路由。
+
+## 验证
+
+- 后端 `mvn -o -pl zksy-system,zksy-admin -am -DskipTests compile` 通过。
+- 前端 `npm run build:prod` 通过;仅有项目既有 Sass 弃用与大 chunk 警告。
+- 两仓 `git diff --check` 通过。
+
+## 风险
+
+- 菜单脚本需要在 KingbaseES 执行后生效;生产库需先维护各设备类型的 `equipment_warning_type` 指标数据。
+- 当前统一页面默认根类型为供水,旧入口 wrapper 分别传入 1/2/3;动态菜单通过 query.rootTypeId 传入根类型。
+
+## 菜单脚本修订
+
+- 旧阈值入口改为按固定 `menu_id` 精准更新:2119(供水)、2194(排水)、2017(窨井)、2192(燃气)。
+- 新增固定两级菜单:2241“阈值管理”(`parent_id=2006`)和 2242“报警阈值”,子菜单指向统一页面。
+- 脚本不再使用菜单名称或模糊路径作为更新条件。
+
+## 旧功能清理
+
+- 删除供水、排水、燃气及窨井旧阈值配置 Vue 页面和供水阈值专属测试。
+- 删除旧 `/warningThreshold/*` 管理 Controller、供水专属阈值 Controller/Service/DTO 及测试。
+- 删除窨井旧阈值设置页、`saveWarningThreshold` 专属 API/Controller 入口及其入参 DTO;保留报警列表、设备详情读取能力。
+- 删除前端旧阈值 API、payload 工具函数和测试;保留水务 UI 模型中仍被 GIS/设备/报警使用的通用函数。
+- 精简通用 `WarningThresholdService` 为底层 CRUD,统一管理查询和批量配置由 `EquipmentThresholdService` 承担;实体与 Mapper 保留供运行时读取和新服务复用。
+
+## 清理后验证
+
+- `npm run build:prod` 通过(仅既有 Sass 弃用及大 chunk 警告)。
+- `node --test tests/*.test.mjs`:125 项全部通过。
+- `mvn -o -pl zksy-system,zksy-admin -am -DskipTests compile` 通过。
+- 两仓 `git diff --check` 通过;旧页面、旧管理路径和已删除 DTO 无残留引用。

+ 25 - 0
memory/sessions/2026-09-22.md

@@ -0,0 +1,25 @@
+# 2026-09-22:报警阈值移除备注字段
+
+## 目标
+
+- 删除 `warning_threshold.remark`,同步阈值管理列表、编辑/新增/批量配置表单及相关后端模型。
+
+## 完成内容
+
+- 主后端 `WarningThreshold`、保存请求项、查询项及保存服务不再映射、读取或提交 `warning_threshold.remark`。
+- 迁移脚本增加 `ALTER TABLE app_user.warning_threshold DROP COLUMN IF EXISTS remark;`,并同步清理阈值测试数据脚本中的 `warning_type`、`remark` 列和值。
+- 前端报警阈值页面移除列表备注列、指标表备注列/输入框、编辑备注表单和提交字段;指标编辑表按原型保持“监测指标 / 单位 / 阈值范围 / 生效时段”四列,并优化编辑设备信息布局。
+- 根据最新运行截图继续调整前端:新增配置、批量配置指标表移除独立“单位”列,将单位显示保留在阈值输入值后;列表监测指标列改为可伸缩列,操作列固定在右侧以消除右侧空白。
+- 阈值列表接口新增已配置设备筛选,主列表只请求 `warning_threshold` 中已有记录的设备;服务端和前端均按设备编码升序、同设备按阈值创建时间降序排列,并在设备编码下显示设备名称。
+- 阈值列表改为 `/warning/threshold/findByPage` 后端分页接口,支持设备编码、设备名称、监测指标条件;列表新增独立设备名称列,单条编辑复用批量配置的指标/时间段编辑器;`/warning/threshold/devices?configuredOnly=true` 同时只返回实际已配置的阈值指标。
+- 旧 `zk-api-service` 阈值实体、Mapper、查询服务以及采集服务引用同步适配单设备编码和新字段,避免继续查询已删除列;窨井盖阈值输出 DTO 移除旧字段。
+
+## 验证
+
+- `city-life-line`: `npm run build:prod` 通过(仅有既有 Sass 弃用警告)。
+- `pipe-network-service`: `mvn -o -pl zksy-system,zksy-admin -am -DskipTests compile` 通过。
+- 根后端旧阈值引用模块:`mvn -o -pl zk-api-service,environment-service,flammable-gas-service,telemetry-service,radar-service,audio-service,water-level-service -am -DskipTests compile` 通过。
+
+## 风险/后续
+
+- 数据库迁移脚本需要在目标 KingbaseES 环境执行后,确认现网 `warning_threshold` 已无 `remark` 列及依赖该列的自定义 SQL。

+ 0 - 23
src/api/drainage.js

@@ -328,26 +328,3 @@ export function bindPumpStation(data) {
 export function addWorkOrder(data) {
   return request({ url: '/manhole/workOrder/addWorkOrder', method: 'post', data })
 }
-
-// ============ 预警阈值配置 ============
-export function getThresholdPage(pageNum, pageSize, params) {
-  return request({ url: '/warningThreshold/findByPage', method: 'get', params: { pageNum, pageSize, ...params } })
-}
-export function getThresholdModulePage(pageNum, pageSize, typeName, params) {
-  return request({ url: '/warningThreshold/findByModulePage', method: 'get', params: { pageNum, pageSize, typeName, ...params } })
-}
-export function getThresholdList(params) {
-  return request({ url: '/warningThreshold/getWarningThresholdList', method: 'get', params })
-}
-export function getThresholdById(id) {
-  return request({ url: '/warningThreshold/getById/' + id, method: 'get' })
-}
-export function saveThreshold(data, moduleType) {
-  return request({ url: '/warningThreshold/save', method: 'post', data, params: moduleType ? { moduleType } : undefined })
-}
-export function updateThreshold(data, moduleType) {
-  return request({ url: '/warningThreshold/update', method: 'post', data, params: moduleType ? { moduleType } : undefined })
-}
-export function deleteThreshold(ids) {
-  return request({ url: '/warningThreshold/deleteBatch', method: 'post', data: ids })
-}

+ 0 - 32
src/api/pipeNetwork/basic.js

@@ -154,10 +154,6 @@ export function getManholeAlarmMonitorList(data = {}) {
 export function getManholeAlarmDeviceDetail(id) {
   return request({ url: '/manhole/device/getById/' + id, method: 'get' })
 }
-export function saveManholeAlarmThreshold(data) {
-  return request({ url: '/manhole/device/saveWarningThreshold', method: 'post', data })
-}
-
 export function getManholeTerminalFaultPage(data) {
   return request({ url: '/manhole/terminalFault/findByPage', method: 'post', data })
 }
@@ -437,31 +433,3 @@ export function getGisManholes() {
 export function getGisEquipments(params) {
   return request({ url: '/api/gis/equipments', method: 'get', params })
 }
-
-// ============ 预警阈值配置 ============
-export function getWarningThresholdPage(pageNum, pageSize, params) {
-  return request({ url: '/warningThreshold/findByPage', method: 'get', params: { pageNum, pageSize, ...params } })
-}
-export function getWarningThresholdModulePage(pageNum, pageSize, typeNameOrParams, moduleParams) {
-  // 新版阈值页面按固定业务模块查询,不再把“燃气”作为查询条件传给后端。
-  // 保留旧的四参数调用方式,兼容其他页面。
-  const params = moduleParams === undefined
-    ? (typeNameOrParams || {})
-    : { typeName: typeNameOrParams, ...(moduleParams || {}) }
-  return request({ url: '/warningThreshold/findByModulePage', method: 'get', params: { pageNum, pageSize, ...params } })
-}
-export function getWarningThresholdList(params) {
-  return request({ url: '/warningThreshold/getWarningThresholdList', method: 'get', params })
-}
-export function getWarningThresholdById(id) {
-  return request({ url: '/warningThreshold/getById/' + id, method: 'get' })
-}
-export function saveWarningThreshold(data, moduleType) {
-  return request({ url: '/warningThreshold/save', method: 'post', data, params: moduleType ? { moduleType } : undefined })
-}
-export function updateWarningThreshold(data, moduleType) {
-  return request({ url: '/warningThreshold/update', method: 'post', data, params: moduleType ? { moduleType } : undefined })
-}
-export function deleteWarningThreshold(ids) {
-  return request({ url: '/warningThreshold/deleteBatch', method: 'post', data: ids })
-}

+ 0 - 7
src/api/pipeNetwork/waterSupply.js

@@ -32,13 +32,6 @@ export const listAlarmLedger = (params) => get('/waterSupply/alarm/ledger/page',
 export const getAlarmLedgerDashboard = (days = 7) => get('/waterSupply/alarm/ledger/dashboard', { days })
 export const exportAlarmLedger = (params) => request({ url: '/waterSupply/alarm/ledger/export', method: 'post', params, responseType: 'blob' })
 
-export const listThresholds = (params) => get('/waterSupply/alarm/threshold/page', params)
-export const listThresholdDevices = () => get('/waterSupply/alarm/threshold/devices')
-export const getThreshold = (id) => get(`/waterSupply/alarm/threshold/detail/${id}`)
-export const saveThresholds = (data) => request({ url: '/waterSupply/alarm/threshold/save', method: 'post', data })
-export const updateThreshold = (data) => request({ url: '/waterSupply/alarm/threshold/update', method: 'put', data })
-export const deleteThresholds = (ids) => request({ url: '/waterSupply/alarm/threshold/delete', method: 'delete', params: { ids } })
-
 export const createWaterDevice = (data) => request({ url: '/waterSupply/device/ledger/create', method: 'post', data })
 export const getWaterDeviceById = (id) => get(`/EquipmentBase/getById/${id}`)
 export const updateWaterDevice = (data) => request({ url: '/EquipmentBase/updateById', method: 'put', data })

+ 21 - 0
src/api/warningThreshold.js

@@ -0,0 +1,21 @@
+import request from '@/utils/request'
+
+export function listWarningTypes(rootTypeId) {
+  return request({ url: '/warning/threshold/types', method: 'get', params: { rootTypeId } })
+}
+
+export function listWarningThresholdDevices(params) {
+  return request({ url: '/warning/threshold/devices', method: 'get', params })
+}
+
+export function pageWarningThresholds(params) {
+  return request({ url: '/warning/threshold/findByPage', method: 'get', params })
+}
+
+export function saveWarningThresholds(data) {
+  return request({ url: '/warning/threshold', method: 'put', data })
+}
+
+export function deleteWarningThreshold(params) {
+  return request({ url: '/warning/threshold', method: 'delete', params })
+}

+ 0 - 64
src/utils/waterSupplyUiModel.js

@@ -194,67 +194,3 @@ export function getDeviceOrderEndpoint(view) {
     ? '/waterSupply/device/abnormal/order'
     : null
 }
-
-function normalizeThresholdValue(value) {
-  if (value === '' || value === null || value === undefined) {
-    return undefined
-  }
-  const parsed = Number(value)
-  return Number.isFinite(parsed) ? parsed : value
-}
-
-function normalizeThresholdPeriod(form = {}) {
-  const periodStart = form.periodStart?.trim() || undefined
-  const periodEnd = form.periodEnd?.trim() || undefined
-  return { periodStart, periodEnd }
-}
-
-function removeUndefined(payload) {
-  return Object.fromEntries(
-    Object.entries(payload).filter(([, value]) => value !== undefined)
-  )
-}
-
-export function buildThresholdSavePayload(form = {}) {
-  const deviceCodes = Array.from(new Set((form.deviceCodes || [])
-    .map(code => code?.trim())
-    .filter(Boolean)))
-  const { periodStart, periodEnd } = normalizeThresholdPeriod(form)
-
-  return removeUndefined({
-    deviceCodes,
-    warningType: form.warningType?.trim() || undefined,
-    warningCode: form.warningCode?.trim() || undefined,
-    minValue: normalizeThresholdValue(form.minValue),
-    maxValue: normalizeThresholdValue(form.maxValue),
-    periodStart,
-    periodEnd,
-    remark: form.remark?.trim() || undefined
-  })
-}
-
-export function buildThresholdUpdatePayload(form = {}) {
-  const { periodStart, periodEnd } = normalizeThresholdPeriod(form)
-
-  return removeUndefined({
-    id: form.id,
-    deviceCode: form.deviceCode?.trim() || undefined,
-    warningType: form.warningType?.trim() || undefined,
-    warningCode: form.warningCode?.trim() || undefined,
-    minValue: normalizeThresholdValue(form.minValue),
-    maxValue: normalizeThresholdValue(form.maxValue),
-    periodStart,
-    periodEnd,
-    remark: form.remark?.trim() || undefined
-  })
-}
-
-export function buildThresholdQuery(query = {}) {
-  return removeUndefined({
-    pageNum: query.pageNum || 1,
-    pageSize: query.pageSize || 10,
-    deviceCode: query.deviceCode?.trim() || undefined,
-    warningType: query.warningType?.trim() || undefined,
-    keyword: query.keyword?.trim() || undefined
-  })
-}

+ 0 - 308
src/views/subSystem/basic/GasThreshold.vue

@@ -1,308 +0,0 @@
-<template>
-  <div class="app-container">
-    <!-- 搜索区 -->
-    <el-card class="search-card">
-      <el-form :model="queryParams" ref="queryRef" :inline="true" class="search-form">
-        <el-form-item label="设备编码">
-          <el-input v-model="queryParams.deviceCode" placeholder="请输入设备编码" clearable style="width: 180px" />
-        </el-form-item>
-        <el-form-item>
-          <el-button type="primary" @click="handleQuery">查询</el-button>
-          <el-button @click="resetQuery">重置</el-button>
-          <el-button type="success" @click="handleAdd">按设备类型批量修改阈值</el-button>
-        </el-form-item>
-      </el-form>
-    </el-card>
-
-    <!-- 表格 -->
-    <el-card class="table-card">
-      <el-table :data="tableData" v-loading="loading" border style="width: 100%">
-        <el-table-column label="设备编码" prop="deviceCode" min-width="140" show-overflow-tooltip />
-        <el-table-column label="设备名称" min-width="140" show-overflow-tooltip>
-          <template #default="{ row }">
-            {{ row.equipmentName || getDeviceName(row.deviceCode) }}
-          </template>
-        </el-table-column>
-        <el-table-column label="设备类型" min-width="120" show-overflow-tooltip>
-          <template #default="{ row }">
-            {{ row.equipmentTypeName || getDeviceType(row.deviceCode) }}
-          </template>
-        </el-table-column>
-        <el-table-column label="最小值" prop="minValue" width="90" align="center" />
-        <el-table-column label="最大值" prop="maxValue" width="90" align="center" />
-        <el-table-column label="备注" prop="remark" min-width="120" show-overflow-tooltip />
-        <el-table-column label="操作" width="160" align="center" fixed="right">
-          <template #default="{ row }">
-            <el-button v-if="row.id" type="primary" size="small" @click="handleEdit(row)">编辑</el-button>
-            <span v-else class="muted">未配置</span>
-            <el-button v-if="row.id" type="danger" size="small" @click="handleDelete(row)">删除</el-button>
-          </template>
-        </el-table-column>
-      </el-table>
-      <el-pagination
-        v-model:current-page="pageNum" v-model:page-size="pageSize" :total="total"
-        layout="total, prev, pager, next, sizes" :page-sizes="[10, 20, 50]"
-        @size-change="handleSizeChange" @current-change="handlePageChange"
-        style="margin-top: 16px; justify-content: flex-end"
-      />
-    </el-card>
-
-    <!-- 新增/编辑对话框 -->
-    <el-dialog :title="dialogTitle" v-model="dialogVisible" width="600px" destroy-on-close>
-      <el-form :model="formData" :rules="rules" ref="formRef" label-width="100px">
-        <el-form-item label="设备类型" prop="deviceType">
-          <el-select v-model="formData.deviceType" placeholder="请选择设备类型" filterable clearable style="width: 100%" @change="onDeviceTypeChange">
-            <el-option v-for="type in deviceTypeOptions" :key="type" :label="type" :value="type" />
-          </el-select>
-        </el-form-item>
-        <el-row :gutter="20">
-          <el-col :span="12">
-            <el-form-item label="最小值" prop="minValue">
-              <el-input-number v-model="formData.minValue" :precision="2" :min="0" style="width: 100%" placeholder="阈值最小值" />
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="最大值" prop="maxValue">
-              <el-input-number v-model="formData.maxValue" :precision="2" :min="0" style="width: 100%" placeholder="阈值最大值" />
-            </el-form-item>
-          </el-col>
-        </el-row>
-        <el-form-item label="备注">
-          <el-input v-model="formData.remark" type="textarea" :rows="3" placeholder="备注信息" />
-        </el-form-item>
-      </el-form>
-      <template #footer>
-        <el-button @click="dialogVisible = false">取消</el-button>
-        <el-button type="primary" @click="handleSave" :loading="submitLoading">确定</el-button>
-      </template>
-    </el-dialog>
-  </div>
-</template>
-
-<script setup name="GasThreshold">
-import { ref, reactive, onMounted } from 'vue'
-import { ElMessage, ElMessageBox } from 'element-plus'
-import { getWarningThresholdModulePage, saveWarningThreshold, updateWarningThreshold, deleteWarningThreshold, getEquipmentByTopLevelType } from '@/api/pipeNetwork/basic'
-
-const GAS_MODULE_TYPE = '燃气'
-
-const deviceTypeOptions = ref([])
-const filteredDeviceOptions = ref([])
-
-// ==================== 搜索参数 ====================
-const queryParams = reactive({
-  deviceCode: ''
-})
-const queryRef = ref(null)
-
-function handleQuery() {
-  pageNum.value = 1
-  loadData()
-}
-function resetQuery() {
-  queryParams.deviceCode = ''
-  pageNum.value = 1
-  loadData()
-}
-
-// ==================== 表格数据 ====================
-const tableData = ref([])
-const total = ref(0)
-const loading = ref(false)
-const pageNum = ref(1)
-const pageSize = ref(10)
-
-async function loadData() {
-  loading.value = true
-  try {
-    const params = {}
-    if (queryParams.deviceCode) params.deviceCode = queryParams.deviceCode
-    const res = await getWarningThresholdModulePage(pageNum.value, pageSize.value, params)
-    const pageData = res.data || res
-    tableData.value = pageData.records || []
-    total.value = pageData.total || 0
-  } catch (e) {
-    console.error('加载阈值列表失败', e)
-    ElMessage.error('加载阈值列表失败')
-  } finally {
-    loading.value = false
-  }
-}
-
-function handlePageChange() { loadData() }
-function handleSizeChange() { pageNum.value = 1; loadData() }
-
-// ==================== 设备名称/类型映射 ====================
-const deviceInfoMap = ref({})
-
-async function ensureDeviceMap() {
-  if (Object.keys(deviceInfoMap.value).length > 0) return
-  try {
-    const res = await getEquipmentByTopLevelType(GAS_MODULE_TYPE)
-    const list = res.data || []
-    const map = {}
-    list.forEach(d => { map[d.equipmentCode] = d })
-    deviceInfoMap.value = map
-    deviceTypeOptions.value = [...new Set(list.map(d => d.equipmentTypeName).filter(Boolean))]
-    filteredDeviceOptions.value = list
-  } catch (e) { console.error('加载燃气设备列表失败', e) }
-}
-
-function getDeviceName(deviceCode) {
-  return String(deviceCode || '').split(',').map(code => {
-    const d = deviceInfoMap.value[code.trim()]
-    return d ? (d.equipmentName || code.trim()) : code.trim()
-  }).filter(Boolean).join('、') || '-'
-}
-
-function getDeviceType(deviceCode) {
-  const types = String(deviceCode || '').split(',').map(code => deviceInfoMap.value[code.trim()])
-    .filter(Boolean).map(d => d.equipmentTypeName).filter(Boolean)
-  return [...new Set(types)].join('、') || '-'
-}
-
-// ==================== 新增/编辑 ====================
-const dialogVisible = ref(false)
-const dialogTitle = ref('按设备类型批量修改阈值')
-const formRef = ref(null)
-const submitLoading = ref(false)
-const isEdit = ref(false)
-
-const rules = {
-  deviceType: [{ required: true, message: '请选择设备类型', trigger: 'change' }]
-}
-
-const formData = reactive({
-  id: '',
-  deviceType: '',
-  deviceCodes: [],
-  deviceCode: '',
-  minValue: null,
-  maxValue: null,
-  remark: ''
-})
-
-const deviceOptions = ref([])
-
-async function loadGasDeviceOptions() {
-  if (deviceOptions.value.length > 0) return
-  try {
-    const res = await getEquipmentByTopLevelType(GAS_MODULE_TYPE)
-    deviceOptions.value = res.data || []
-    deviceTypeOptions.value = [...new Set(deviceOptions.value.map(d => d.equipmentTypeName).filter(Boolean))]
-    filteredDeviceOptions.value = deviceOptions.value
-  } catch (e) {
-    console.error('加载燃气设备列表失败', e)
-  }
-}
-
-function resetForm() {
-  formData.id = ''
-  formData.deviceType = ''
-  formData.deviceCodes = []
-  formData.deviceCode = ''
-  formData.minValue = null
-  formData.maxValue = null
-  formData.remark = ''
-  isEdit.value = false
-  dialogTitle.value = '按设备类型批量修改阈值'
-}
-
-function onDeviceTypeChange(type) {
-  filteredDeviceOptions.value = deviceOptions.value.filter(d => d.equipmentTypeName === type)
-  formData.deviceCodes = filteredDeviceOptions.value.map(d => d.equipmentCode)
-}
-
-async function handleAdd() {
-  resetForm()
-  await loadGasDeviceOptions()
-  dialogVisible.value = true
-}
-
-async function handleEdit(row) {
-  await loadGasDeviceOptions()
-  isEdit.value = true
-  dialogTitle.value = '编辑阈值'
-  formData.id = row.id || ''
-  formData.deviceCode = row.deviceCode
-  formData.deviceCodes = String(row.deviceCode || '').split(',').map(v => v.trim()).filter(Boolean)
-  const firstDevice = deviceOptions.value.find(d => formData.deviceCodes.includes(d.equipmentCode))
-  formData.deviceType = firstDevice?.equipmentTypeName || ''
-  onDeviceTypeChange(formData.deviceType)
-  formData.minValue = row.minValue
-  formData.maxValue = row.maxValue
-  formData.remark = row.remark || ''
-  dialogVisible.value = true
-}
-
-async function handleSave() {
-  const valid = await formRef.value.validate().catch(() => false)
-  if (!valid) return
-  if (!formData.deviceCodes.length) {
-    ElMessage.warning('该设备类型下没有可配置的设备')
-    return
-  }
-  submitLoading.value = true
-  try {
-    const data = {
-      deviceCode: formData.deviceCodes.join(','),
-      minValue: formData.minValue,
-      maxValue: formData.maxValue,
-      remark: formData.remark || undefined
-    }
-    if (isEdit.value) {
-      data.id = formData.id
-      await updateWarningThreshold(data, GAS_MODULE_TYPE)
-      ElMessage.success('设备类型阈值批量修改成功')
-    } else {
-      await saveWarningThreshold(data, GAS_MODULE_TYPE)
-      ElMessage.success('设备类型阈值批量配置成功')
-    }
-    dialogVisible.value = false
-    loadData()
-    // 刷新设备映射
-    deviceInfoMap.value = {}
-    await ensureDeviceMap()
-  } catch (e) {
-    const msg = e?.response?.data?.msg || e?.message || '操作失败'
-    ElMessage.error(msg)
-  } finally {
-    submitLoading.value = false
-  }
-}
-
-// ==================== 删除 ====================
-function handleDelete(row) {
-  ElMessageBox.confirm('确认删除该阈值配置?', '提示', { type: 'warning' }).then(async () => {
-    try {
-      await deleteWarningThreshold([row.id])
-      ElMessage.success('删除成功')
-      loadData()
-    } catch (e) {
-      ElMessage.error(e?.response?.data?.msg || '删除失败')
-    }
-  }).catch(() => {})
-}
-
-// ==================== 生命周期 ====================
-onMounted(async () => {
-  await ensureDeviceMap()
-  loadData()
-})
-</script>
-
-<style scoped>
-.app-container {
-  padding: 16px;
-}
-.search-card {
-  margin-bottom: 16px;
-}
-.search-form {
-  display: flex;
-  flex-wrap: wrap;
-}
-.table-card {
-  min-height: 400px;
-}
-</style>

+ 0 - 311
src/views/subSystem/drainage/jcyj/DrainageThreshold.vue

@@ -1,311 +0,0 @@
-<template>
-  <div class="app-container">
-    <!-- 搜索区 -->
-    <el-card class="search-card">
-      <el-form :model="queryParams" ref="queryRef" :inline="true" class="search-form">
-        <el-form-item label="设备编码">
-          <el-input v-model="queryParams.deviceCode" placeholder="请输入设备编码" clearable style="width: 180px" />
-        </el-form-item>
-        <el-form-item>
-          <el-button type="primary" @click="handleQuery">查询</el-button>
-          <el-button @click="resetQuery">重置</el-button>
-          <el-button type="success" @click="handleAdd">按设备类型批量修改阈值</el-button>
-        </el-form-item>
-      </el-form>
-    </el-card>
-
-    <!-- 表格 -->
-    <el-card class="table-card">
-      <el-table :data="tableData" v-loading="loading" border style="width: 100%">
-        <el-table-column label="设备编码" prop="deviceCode" min-width="140" show-overflow-tooltip />
-        <el-table-column label="设备名称" min-width="140" show-overflow-tooltip>
-          <template #default="{ row }">
-            {{ row.equipmentName || getDeviceName(row.deviceCode) }}
-          </template>
-        </el-table-column>
-        <el-table-column label="设备类型" min-width="120" show-overflow-tooltip>
-          <template #default="{ row }">
-            {{ row.equipmentTypeName || getDeviceType(row.deviceCode) }}
-          </template>
-        </el-table-column>
-        <el-table-column label="最小值" prop="minValue" width="90" align="center" />
-        <el-table-column label="最大值" prop="maxValue" width="90" align="center" />
-        <el-table-column label="备注" prop="remark" min-width="120" show-overflow-tooltip />
-        <el-table-column label="创建时间" prop="createTime" width="170" />
-        <el-table-column label="操作" width="160" align="center" fixed="right">
-          <template #default="{ row }">
-            <el-button v-if="row.id" type="primary" size="small" @click="handleEdit(row)">编辑</el-button>
-            <span v-else class="muted">未配置</span>
-            <el-button v-if="row.id" type="danger" size="small" @click="handleDelete(row)">删除</el-button>
-          </template>
-        </el-table-column>
-      </el-table>
-      <el-pagination
-        v-model:current-page="pageNum" v-model:page-size="pageSize" :total="total"
-        layout="total, prev, pager, next, sizes" :page-sizes="[10, 20, 50]"
-        @size-change="handleSizeChange" @current-change="handlePageChange"
-        style="margin-top: 16px; justify-content: flex-end"
-      />
-    </el-card>
-
-    <!-- 新增/编辑对话框 -->
-    <el-dialog :title="dialogTitle" v-model="dialogVisible" width="600px" destroy-on-close>
-      <el-form :model="formData" :rules="rules" ref="formRef" label-width="100px">
-        <el-form-item label="设备类型" prop="deviceType">
-          <el-select v-model="formData.deviceType" placeholder="请选择设备类型" filterable clearable style="width: 100%" @change="onDeviceTypeChange">
-            <el-option v-for="type in deviceTypeOptions" :key="type" :label="type" :value="type" />
-          </el-select>
-        </el-form-item>
-        <el-row :gutter="20">
-          <el-col :span="12">
-            <el-form-item label="最小值" prop="minValue">
-              <el-input-number v-model="formData.minValue" :precision="2" :min="0" style="width: 100%" placeholder="阈值最小值" />
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="最大值" prop="maxValue">
-              <el-input-number v-model="formData.maxValue" :precision="2" :min="0" style="width: 100%" placeholder="阈值最大值" />
-            </el-form-item>
-          </el-col>
-        </el-row>
-        <el-form-item label="备注">
-          <el-input v-model="formData.remark" type="textarea" :rows="3" placeholder="备注信息" />
-        </el-form-item>
-      </el-form>
-      <template #footer>
-        <el-button @click="dialogVisible = false">取消</el-button>
-        <el-button type="primary" @click="handleSave" :loading="submitLoading">确定</el-button>
-      </template>
-    </el-dialog>
-  </div>
-</template>
-
-<script setup name="DrainageThreshold">
-import { ref, reactive, onMounted } from 'vue'
-import { ElMessage, ElMessageBox } from 'element-plus'
-import {
-  getThresholdModulePage, saveThreshold, updateThreshold, deleteThreshold
-} from '@/api/drainage'
-import { getEquipmentByTopLevelType } from '@/api/pipeNetwork/basic'
-
-const DRAINAGE_MODULE_TYPE = '排水'
-const deviceTypeOptions = ref([])
-const filteredDeviceOptions = ref([])
-
-// ==================== 搜索参数 ====================
-const queryParams = reactive({
-  deviceCode: ''
-})
-const queryRef = ref(null)
-
-function handleQuery() {
-  pageNum.value = 1
-  loadData()
-}
-function resetQuery() {
-  queryParams.deviceCode = ''
-  pageNum.value = 1
-  loadData()
-}
-
-// ==================== 表格数据 ====================
-const tableData = ref([])
-const total = ref(0)
-const loading = ref(false)
-const pageNum = ref(1)
-const pageSize = ref(10)
-
-async function loadData() {
-  loading.value = true
-  try {
-    const params = {}
-    if (queryParams.deviceCode) params.deviceCode = queryParams.deviceCode
-    const res = await getThresholdModulePage(pageNum.value, pageSize.value, DRAINAGE_MODULE_TYPE, params)
-    const pageData = res.data || res
-    tableData.value = pageData.records || []
-    total.value = pageData.total || 0
-  } catch (e) {
-    console.error('加载阈值列表失败', e)
-    ElMessage.error('加载阈值列表失败')
-  } finally {
-    loading.value = false
-  }
-}
-
-function handlePageChange() { loadData() }
-function handleSizeChange() { pageNum.value = 1; loadData() }
-
-// ==================== 设备名称/类型映射 ====================
-const deviceInfoMap = ref({})
-
-async function ensureDeviceMap() {
-  if (Object.keys(deviceInfoMap.value).length > 0) return
-  try {
-    const res = await getEquipmentByTopLevelType(DRAINAGE_MODULE_TYPE)
-    const list = res.data || []
-    const map = {}
-    list.forEach(d => { map[d.equipmentCode] = d })
-    deviceInfoMap.value = map
-    deviceTypeOptions.value = [...new Set(list.map(d => d.equipmentTypeName).filter(Boolean))]
-    filteredDeviceOptions.value = list
-  } catch (e) { console.error('加载排水设备列表失败', e) }
-}
-
-function getDeviceName(deviceCode) {
-  return String(deviceCode || '').split(',').map(code => {
-    const d = deviceInfoMap.value[code.trim()]
-    return d ? (d.equipmentName || code.trim()) : code.trim()
-  }).filter(Boolean).join('、') || '-'
-}
-
-function getDeviceType(deviceCode) {
-  const types = String(deviceCode || '').split(',').map(code => deviceInfoMap.value[code.trim()])
-    .filter(Boolean).map(d => d.equipmentTypeName).filter(Boolean)
-  return [...new Set(types)].join('、') || '-'
-}
-
-// ==================== 新增/编辑 ====================
-const dialogVisible = ref(false)
-const dialogTitle = ref('按设备类型批量修改阈值')
-const formRef = ref(null)
-const submitLoading = ref(false)
-const isEdit = ref(false)
-
-const rules = {
-  deviceType: [{ required: true, message: '请选择设备类型', trigger: 'change' }]
-}
-
-const formData = reactive({
-  id: '',
-  deviceType: '',
-  deviceCodes: [],
-  deviceCode: '',
-  minValue: null,
-  maxValue: null,
-  remark: ''
-})
-
-const deviceOptions = ref([])
-
-async function loadDrainageDeviceOptions() {
-  if (deviceOptions.value.length > 0) return
-  try {
-    const res = await getEquipmentByTopLevelType(DRAINAGE_MODULE_TYPE)
-    deviceOptions.value = res.data || []
-    deviceTypeOptions.value = [...new Set(deviceOptions.value.map(d => d.equipmentTypeName).filter(Boolean))]
-    filteredDeviceOptions.value = deviceOptions.value
-  } catch (e) {
-    console.error('加载排水设备列表失败', e)
-  }
-}
-
-function resetForm() {
-  formData.id = ''
-  formData.deviceType = ''
-  formData.deviceCodes = []
-  formData.deviceCode = ''
-  formData.minValue = null
-  formData.maxValue = null
-  formData.remark = ''
-  isEdit.value = false
-  dialogTitle.value = '按设备类型批量修改阈值'
-}
-
-function onDeviceTypeChange(type) {
-  filteredDeviceOptions.value = deviceOptions.value.filter(d => d.equipmentTypeName === type)
-  formData.deviceCodes = filteredDeviceOptions.value.map(d => d.equipmentCode).filter(Boolean)
-}
-
-async function handleAdd() {
-  resetForm()
-  await loadDrainageDeviceOptions()
-  dialogVisible.value = true
-}
-
-async function handleEdit(row) {
-  await loadDrainageDeviceOptions()
-  isEdit.value = true
-  dialogTitle.value = '编辑阈值'
-  formData.id = row.id || ''
-  formData.deviceCode = row.deviceCode
-  formData.deviceCodes = String(row.deviceCode || '').split(',').map(v => v.trim()).filter(Boolean)
-  const firstDevice = deviceOptions.value.find(d => formData.deviceCodes.includes(d.equipmentCode))
-  formData.deviceType = firstDevice?.equipmentTypeName || ''
-  onDeviceTypeChange(formData.deviceType)
-  formData.minValue = row.minValue
-  formData.maxValue = row.maxValue
-  formData.remark = row.remark || ''
-  dialogVisible.value = true
-}
-
-async function handleSave() {
-  const valid = await formRef.value.validate().catch(() => false)
-  if (!valid) return
-  if (!formData.deviceCodes.length) {
-    ElMessage.warning('该设备类型下没有可配置的设备')
-    return
-  }
-  submitLoading.value = true
-  try {
-    const data = {
-      deviceCode: formData.deviceCodes.join(','),
-      minValue: formData.minValue,
-      maxValue: formData.maxValue,
-      remark: formData.remark || undefined
-    }
-    if (isEdit.value) {
-      data.id = formData.id
-      await updateThreshold(data, DRAINAGE_MODULE_TYPE)
-      ElMessage.success('设备类型阈值批量修改成功')
-    } else {
-      await saveThreshold(data, DRAINAGE_MODULE_TYPE)
-      ElMessage.success('设备类型阈值批量配置成功')
-    }
-    dialogVisible.value = false
-    loadData()
-    // 刷新设备映射
-    deviceInfoMap.value = {}
-    await ensureDeviceMap()
-  } catch (e) {
-    const msg = e?.response?.data?.msg || e?.message || '操作失败'
-    ElMessage.error(msg)
-  } finally {
-    submitLoading.value = false
-  }
-}
-
-// ==================== 删除 ====================
-function handleDelete(row) {
-  ElMessageBox.confirm('确认删除该阈值配置?', '提示', { type: 'warning' }).then(async () => {
-    try {
-      await deleteThreshold([row.id])
-      ElMessage.success('删除成功')
-      loadData()
-    } catch (e) {
-      ElMessage.error(e?.response?.data?.msg || '删除失败')
-    }
-  }).catch(() => {})
-}
-
-// ==================== 生命周期 ====================
-onMounted(async () => {
-  await ensureDeviceMap()
-  loadData()
-})
-</script>
-
-<style scoped>
-.app-container {
-  padding: 16px;
-}
-.search-card {
-  margin-bottom: 16px;
-}
-.search-form {
-  display: flex;
-  flex-wrap: wrap;
-}
-.table-card {
-  min-height: 400px;
-}
-</style>

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 33 - 0
src/views/subSystem/lifeCompany/warningThreshold/index.vue


+ 0 - 668
src/views/subSystem/manholeCover/alarm/index.vue

@@ -1,668 +0,0 @@
-<template>
-  <div class="threshold-management">
-    <div class="main-content">
-      <div class="point-list-panel">
-        <div class="panel-title">
-          <span>设备列表</span>
-          <el-button :icon="Refresh" text @click="loadDeviceList">刷新</el-button>
-        </div>
-        <el-input
-          v-model="searchKeyword"
-          placeholder="搜索设备编号/名称"
-          clearable
-          :prefix-icon="Search"
-          size="small"
-          @input="handleSearch"
-        />
-
-        <div v-loading="listLoading" class="point-list">
-          <div
-            v-for="point in deviceList"
-            :key="point.equipmentId"
-            class="point-item"
-            :class="{ active: selectedPoint?.equipmentId === point.equipmentId }"
-            @click="selectPoint(point)"
-          >
-            <div class="point-header">
-              <span class="point-name">{{ point.equipmentName || point.equipmentCode || '未命名设备' }}</span>
-              <el-tag :type="getAlarmTagType(point)" size="small">
-                {{ getAlarmText(point) }}
-              </el-tag>
-            </div>
-            <div class="point-location">
-              <el-icon><Location /></el-icon>
-              <span>{{ point.equipmentLocation || '暂无位置' }}</span>
-            </div>
-            <div class="point-meta">
-              <span>编号:{{ point.equipmentCode || '-' }}</span>
-              <span>类型:{{ point.equipmentTypeName || '-' }}</span>
-            </div>
-            <div class="point-status-row">
-              <el-tag size="small" effect="plain" :type="getOnlineTagType(point.equipmentStatus?.onlineStatus)">
-                {{ getOnlineText(point.equipmentStatus?.onlineStatus) }}
-              </el-tag>
-              <el-tag size="small" effect="plain" :type="getUseStatusTagType(point.equipmentStatus?.currentStatus)">
-                {{ getUseStatusText(point.equipmentStatus?.currentStatus) }}
-              </el-tag>
-            </div>
-          </div>
-
-          <el-empty
-            v-if="!listLoading && !deviceList.length"
-            description="暂无井盖监测设备"
-            :image-size="120"
-          />
-        </div>
-
-        <div class="pagination-area">
-          <el-pagination
-            small
-            background
-            layout="total, prev, pager, next"
-            :total="total"
-            :page-size="pageSize"
-            v-model:current-page="currentPage"
-            @current-change="loadDeviceList"
-          />
-        </div>
-      </div>
-
-      <div v-if="selectedPoint" v-loading="detailLoading" class="config-panel">
-        <div class="info-card">
-          <div class="card-title">
-            <span><el-icon><Monitor /></el-icon> 设备基本信息</span>
-          </div>
-          <el-descriptions :column="2" border>
-            <el-descriptions-item label="设备编号">{{ selectedDetail?.equipmentCode || '-' }}</el-descriptions-item>
-            <el-descriptions-item label="设备名称">{{ selectedDetail?.equipmentName || '-' }}</el-descriptions-item>
-            <el-descriptions-item label="设备类型">{{ selectedDetail?.equipmentTypeName || '-' }}</el-descriptions-item>
-            <el-descriptions-item label="设备型号">{{ selectedDetail?.equipmentModel || '-' }}</el-descriptions-item>
-            <el-descriptions-item label="生产厂商">{{ selectedDetail?.manufacturer || '-' }}</el-descriptions-item>
-            <el-descriptions-item label="安装时间">{{ formatDateTime(selectedDetail?.installTime) }}</el-descriptions-item>
-            <el-descriptions-item label="设备位置">{{ selectedDetail?.equipmentLocation || '-' }}</el-descriptions-item>
-            <el-descriptions-item label="维护人员">{{ selectedDetail?.maintainer || '-' }}</el-descriptions-item>
-          </el-descriptions>
-        </div>
-
-        <div class="info-card">
-          <div class="card-title">
-            <span><el-icon><Warning /></el-icon> 最新监测状态</span>
-          </div>
-          <div class="status-grid">
-            <div class="status-item">
-              <div class="status-label">在线状态</div>
-              <div class="status-value">{{ getOnlineText(selectedDetail?.equipmentStatus?.onlineStatus) }}</div>
-            </div>
-            <div class="status-item">
-              <div class="status-label">报警状态</div>
-              <div class="status-value">{{ getAlarmText(selectedDetail) }}</div>
-            </div>
-            <div class="status-item">
-              <div class="status-label">倾斜角度</div>
-              <div class="status-value">{{ appendUnit(selectedDetail?.manholeData?.tiltAngle, '°') }}</div>
-            </div>
-            <div class="status-item">
-              <div class="status-label">电池电量</div>
-              <div class="status-value">{{ appendUnit(selectedDetail?.manholeData?.batteryLevel, '%') }}</div>
-            </div>
-            <div class="status-item">
-              <div class="status-label">信号强度</div>
-              <div class="status-value">{{ appendUnit(selectedDetail?.manholeData?.signalStrength, '%') }}</div>
-            </div>
-            <div class="status-item">
-              <div class="status-label">水浸状态</div>
-              <div class="status-value">{{ getBinaryAlarmText(selectedDetail?.manholeData?.waterInfiltrationAlarmStatus) }}</div>
-            </div>
-            <div class="status-item">
-              <div class="status-label">水位状态</div>
-              <div class="status-value">{{ getBinaryAlarmText(selectedDetail?.manholeData?.waterLevelAlarmStatus) }}</div>
-            </div>
-            <div class="status-item">
-              <div class="status-label">激活状态</div>
-              <div class="status-value">{{ selectedDetail?.manholeData?.activationStatus || '-' }}</div>
-            </div>
-          </div>
-        </div>
-
-        <div class="threshold-card">
-          <div class="card-title">
-            <span><el-icon><Setting /></el-icon> 报警阈值设置</span>
-            <div class="card-actions">
-              <el-button :icon="Refresh" size="small" @click="resetThresholds">重置</el-button>
-              <el-button :loading="saveLoading" size="small" type="primary" @click="saveThresholds">保存设置</el-button>
-            </div>
-          </div>
-
-          <div class="threshold-tip">
-            当前版本先对接后端已支持的 3 类阈值:倾斜、水位、温度。
-          </div>
-
-          <el-tabs v-model="activeTab" class="threshold-tabs">
-            <el-tab-pane
-              v-for="item in thresholdConfigList"
-              :key="item.key"
-              :label="item.label"
-              :name="item.key"
-            >
-              <div class="threshold-item">
-                <div class="item-label">{{ item.desc }}</div>
-                <div class="item-control">
-                  <span class="field-label">最小值</span>
-                  <el-input-number
-                    v-model="thresholds[item.key].minValue"
-                    :controls="false"
-                    :precision="item.precision"
-                    :step="item.step"
-                    class="threshold-input"
-                    placeholder="未设置"
-                  />
-                  <span class="unit">{{ item.unit }}</span>
-
-                  <span class="field-label">最大值</span>
-                  <el-input-number
-                    v-model="thresholds[item.key].maxValue"
-                    :controls="false"
-                    :precision="item.precision"
-                    :step="item.step"
-                    class="threshold-input"
-                    placeholder="未设置"
-                  />
-                  <span class="unit">{{ item.unit }}</span>
-                </div>
-                <el-input
-                  v-model="thresholds[item.key].remark"
-                  maxlength="100"
-                  placeholder="请输入备注"
-                />
-                <div class="saved-hint">
-                  当前阈值配置将保存到后端预警规则
-                </div>
-              </div>
-            </el-tab-pane>
-          </el-tabs>
-        </div>
-      </div>
-
-      <div v-else class="empty-panel">
-        <el-empty description="请从左侧选择设备查看阈值配置" :image-size="140" />
-      </div>
-    </div>
-  </div>
-</template>
-
-<script setup>
-import {onBeforeUnmount, onMounted, ref} from 'vue'
-import {ElMessage} from 'element-plus'
-import {Location, Monitor, Refresh, Search, Setting, Warning} from '@element-plus/icons-vue'
-import {
-  getManholeAlarmDeviceDetail,
-  getManholeDevicePage,
-  saveManholeAlarmThreshold
-} from '@/api/pipeNetwork/basic'
-
-const thresholdConfigList = [
-  {
-    key: 'tilt',
-    label: '倾斜监测',
-    warningType: '倾斜预警',
-    warningCode: 'WARN-TILT',
-    unit: '°',
-    step: 0.1,
-    precision: 1,
-    desc: '当倾斜角度小于等于最小值或大于等于最大值时触发报警'
-  },
-  {
-    key: 'waterLevel',
-    label: '水位监测',
-    warningType: '水位预警',
-    warningCode: 'WARN-WATER-LEVEL',
-    unit: 'cm',
-    step: 0.1,
-    precision: 1,
-    desc: '用于井盖水位报警阈值配置'
-  },
-  {
-    key: 'temperature',
-    label: '温度监测',
-    warningType: '温度预警',
-    warningCode: 'WARN-TEMPERATURE',
-    unit: '℃',
-    step: 0.1,
-    precision: 1,
-    desc: '用于设备温度报警阈值配置'
-  }
-]
-
-const searchKeyword = ref('')
-const activeTab = ref('tilt')
-const listLoading = ref(false)
-const detailLoading = ref(false)
-const saveLoading = ref(false)
-const deviceList = ref([])
-const total = ref(0)
-const currentPage = ref(1)
-const pageSize = 8
-const selectedPoint = ref(null)
-const selectedDetail = ref(null)
-const thresholds = ref(createEmptyThresholds())
-
-// 搜索防抖
-let searchTimer = null
-function handleSearch() {
-  clearTimeout(searchTimer)
-  searchTimer = setTimeout(() => {
-    currentPage.value = 1
-    loadDeviceList()
-  }, 300)
-}
-
-function createEmptyThresholds() {
-  return thresholdConfigList.reduce((acc, item) => {
-    acc[item.key] = {
-      id: '',
-      minValue: null,
-      maxValue: null,
-      remark: ''
-    }
-    return acc
-  }, {})
-}
-
-function normalizeThresholdList(list) {
-  const safeList = Array.isArray(list) ? list : []
-  const result = createEmptyThresholds()
-  thresholdConfigList.forEach((item) => {
-    const matched = safeList.find(threshold => threshold.warningCode === item.warningCode)
-    if (matched) {
-      result[item.key] = {
-        id: matched.id || '',
-        minValue: matched.minValue ?? null,
-        maxValue: matched.maxValue ?? null,
-        remark: matched.remark || ''
-      }
-    }
-  })
-  return result
-}
-
-
-
-function getOnlineText(status) {
-  return Number(status) === 1 ? '在线' : '离线'
-}
-
-function getOnlineTagType(status) {
-  return Number(status) === 1 ? 'success' : 'info'
-}
-
-function getAlarmText(point) {
-  return Number(point?.equipmentStatus?.alarmStatus) === 1 ? '报警' : '正常'
-}
-
-function getAlarmTagType(point) {
-  return Number(point?.equipmentStatus?.alarmStatus) === 1 ? 'danger' : 'success'
-}
-
-function getUseStatusText(status) {
-  const map = {
-    1: '在用',
-    2: '闲置',
-    3: '维修',
-    4: '报废',
-    5: '待入库'
-  }
-  return map[Number(status)] || '未知'
-}
-
-function getUseStatusTagType(status) {
-  const map = {
-    1: 'success',
-    2: 'info',
-    3: 'warning',
-    4: 'danger',
-    5: ''
-  }
-  return map[Number(status)] || 'info'
-}
-
-function getBinaryAlarmText(status) {
-  return `${status}` === '1' ? '报警' : `${status}` === '0' ? '正常' : '-'
-}
-
-function appendUnit(value, unit) {
-  if (value === undefined || value === null || value === '') return '-'
-  return `${value}${unit}`
-}
-
-function formatDateTime(value) {
-  return value || '-'
-}
-
-async function loadDeviceList() {
-  listLoading.value = true
-  try {
-    const res = await getManholeDevicePage({
-      pageNum: currentPage.value,
-      pageSize,
-      deviceName: searchKeyword.value.trim() || null,
-      isQueryManholeData: true
-    })
-    const list = res.data?.records || []
-    deviceList.value = list
-    total.value = res.data?.total || list.length
-
-    if (!list.length) {
-      selectedPoint.value = null
-      selectedDetail.value = null
-      thresholds.value = createEmptyThresholds()
-      return
-    }
-
-    const currentId = selectedPoint.value?.equipmentId
-    const currentPoint = list.find(item => item.equipmentId === currentId) || list[0]
-    await selectPoint(currentPoint)
-  } catch (error) {
-    deviceList.value = []
-    selectedPoint.value = null
-    selectedDetail.value = null
-  } finally {
-    listLoading.value = false
-  }
-}
-
-async function selectPoint(point) {
-  if (!point?.equipmentId) return
-  selectedPoint.value = point
-  await loadDeviceDetail(point.equipmentId)
-}
-
-async function loadDeviceDetail(id) {
-  detailLoading.value = true
-  try {
-    const res = await getManholeAlarmDeviceDetail(id)
-    selectedDetail.value = res?.data || null
-    thresholds.value = normalizeThresholdList(selectedDetail.value?.warningThresholdList)
-  } catch (error) {
-    selectedDetail.value = null
-    thresholds.value = createEmptyThresholds()
-  } finally {
-    detailLoading.value = false
-  }
-}
-
-async function resetThresholds() {
-  if (!selectedPoint.value?.equipmentId) return
-  await loadDeviceDetail(selectedPoint.value.equipmentId)
-  ElMessage.success('已恢复为服务端最新阈值')
-}
-
-function hasThresholdValue(item) {
-  return (
-    (item.minValue !== null && item.minValue !== undefined) ||
-    (item.maxValue !== null && item.maxValue !== undefined)
-  )
-}
-
-async function saveThresholds() {
-  if (!selectedDetail.value?.equipmentCode) {
-    ElMessage.warning('未获取到设备编码,无法保存')
-    return
-  }
-
-  const invalidConfigs = thresholdConfigList.filter((config) => {
-    const formItem = thresholds.value[config.key]
-    return formItem.id && !hasThresholdValue(formItem)
-  })
-
-  if (invalidConfigs.length) {
-    ElMessage.warning(`${invalidConfigs[0].label}请至少保留一个阈值`)
-    return
-  }
-
-  const payloads = thresholdConfigList
-    .map((config) => {
-      const formItem = thresholds.value[config.key]
-      if (!hasThresholdValue(formItem) && !formItem.id) return null
-      return {
-        id: formItem.id || undefined,
-        deviceCode: selectedDetail.value.equipmentCode,
-        warningType: config.warningType,
-        warningCode: config.warningCode,
-        minValue: formItem.minValue,
-        maxValue: formItem.maxValue,
-        remark: formItem.remark || ''
-      }
-    })
-    .filter(Boolean)
-
-  if (!payloads.length) {
-    ElMessage.warning('请先填写至少一项阈值')
-    return
-  }
-
-  saveLoading.value = true
-  try {
-    await Promise.all(payloads.map(item => saveManholeAlarmThreshold(item)))
-    ElMessage.success('阈值保存成功')
-    await loadDeviceDetail(selectedPoint.value.equipmentId)
-  } finally {
-    saveLoading.value = false
-  }
-}
-
-onMounted(() => {
-  loadDeviceList()
-})
-
-onBeforeUnmount(() => {
-  clearTimeout(searchTimer)
-})
-</script>
-
-<style scoped>
-.threshold-management {
-  padding: 20px;
-  background: #f0f2f6;
-  min-height: 100vh;
-}
-
-.main-content {
-  display: flex;
-  gap: 20px;
-}
-
-.point-list-panel {
-  width: 340px;
-  background: #fff;
-  border-radius: 16px;
-  padding: 16px;
-  display: flex;
-  flex-direction: column;
-  gap: 12px;
-}
-
-.panel-title {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  font-weight: 600;
-}
-
-.point-list {
-  max-height: calc(100vh - 180px);
-  overflow-y: auto;
-}
-
-.point-item {
-  border: 1px solid #e5e7eb;
-  border-radius: 12px;
-  padding: 14px;
-  margin-bottom: 12px;
-  cursor: pointer;
-  transition: all 0.2s ease;
-}
-
-.point-item:hover {
-  background: #f8fbff;
-  border-color: #bfdbfe;
-}
-
-.point-item.active {
-  background: #ecf5ff;
-  border-color: #409eff;
-}
-
-.point-header {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  gap: 8px;
-  margin-bottom: 8px;
-}
-
-.point-name {
-  font-weight: 600;
-  color: #303133;
-}
-
-.point-location {
-  display: flex;
-  align-items: center;
-  gap: 4px;
-  color: #606266;
-  font-size: 12px;
-  margin-bottom: 8px;
-}
-
-.point-meta {
-  display: flex;
-  flex-direction: column;
-  gap: 4px;
-  font-size: 12px;
-  color: #409eff;
-  margin-bottom: 8px;
-}
-
-.point-status-row {
-  display: flex;
-  gap: 8px;
-}
-
-.config-panel {
-  flex: 1;
-  display: flex;
-  flex-direction: column;
-  gap: 20px;
-}
-
-.info-card,
-.threshold-card {
-  background: #fff;
-  border-radius: 16px;
-  padding: 18px 20px;
-  box-shadow: 0 2px 8px rgba(15, 23, 42, 0.04);
-}
-
-.card-title {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  margin-bottom: 16px;
-  padding-left: 12px;
-  border-left: 4px solid #409eff;
-  font-size: 16px;
-  font-weight: 600;
-}
-
-.card-actions {
-  display: flex;
-  gap: 8px;
-}
-
-.status-grid {
-  display: grid;
-  grid-template-columns: repeat(4, minmax(0, 1fr));
-  gap: 12px;
-}
-
-.status-item {
-  background: #f8fafc;
-  border: 1px solid #edf2f7;
-  border-radius: 12px;
-  padding: 14px;
-}
-
-.status-label {
-  color: #909399;
-  font-size: 13px;
-  margin-bottom: 8px;
-}
-
-.status-value {
-  color: #303133;
-  font-size: 16px;
-  font-weight: 600;
-}
-
-.threshold-tip {
-  margin-bottom: 12px;
-  padding: 10px 12px;
-  border-radius: 8px;
-  background: #f4f9ff;
-  color: #606266;
-  font-size: 13px;
-}
-
-.threshold-item {
-  padding: 8px 4px 4px;
-}
-
-.item-label {
-  margin-bottom: 16px;
-  color: #606266;
-  font-size: 14px;
-}
-
-.item-control {
-  display: flex;
-  align-items: center;
-  flex-wrap: wrap;
-  gap: 8px;
-  margin-bottom: 16px;
-}
-
-.field-label {
-  color: #303133;
-  font-weight: 500;
-}
-
-.threshold-input {
-  width: 160px;
-}
-
-.unit {
-  color: #909399;
-  margin-right: 16px;
-}
-
-.saved-hint {
-  margin-top: 10px;
-  color: #909399;
-  font-size: 12px;
-}
-
-.empty-panel {
-  flex: 1;
-  display: flex;
-  justify-content: center;
-  align-items: center;
-  background: #fff;
-  border-radius: 16px;
-}
-
-.pagination-area {
-  padding: 8px 0 0;
-  display: flex;
-  justify-content: center;
-}
-</style>

+ 0 - 520
src/views/subSystem/waterSupply/components/WaterThresholdPage.vue

@@ -1,520 +0,0 @@
-<template>
-  <div class="water-page threshold-page">
-    <div class="page-heading">
-      <div class="page-title">
-        <span class="page-title-icon"><el-icon><Warning /></el-icon></span>
-        <div>
-          <h2>阈值管理</h2>
-          <span class="muted">供水监测设备阈值、分时规则与生效范围统一配置</span>
-        </div>
-      </div>
-      <div class="heading-actions">
-        <el-button type="primary" :icon="Plus" @click="openCreate">新增阈值</el-button>
-        <el-button :icon="Refresh" :loading="loading" @click="loadRules">刷新</el-button>
-      </div>
-    </div>
-
-    <div class="metric-grid">
-      <div class="metric">
-        <div class="metric-icon"><el-icon><Connection /></el-icon></div>
-        <div class="metric-body">
-          <span>供水监测设备</span>
-          <div class="metric-value"><strong>{{ devices.length }}</strong><small>台</small></div>
-        </div>
-      </div>
-      <div class="metric">
-        <div class="metric-icon"><el-icon><Bell /></el-icon></div>
-        <div class="metric-body">
-          <span>阈值规则</span>
-          <div class="metric-value"><strong>{{ page.total }}</strong><small>条</small></div>
-        </div>
-      </div>
-      <div class="metric">
-        <div class="metric-icon"><el-icon><CircleCheck /></el-icon></div>
-        <div class="metric-body">
-          <span>当前页全天规则</span>
-          <div class="metric-value"><strong>{{ allDayCount }}</strong><small>条</small></div>
-        </div>
-      </div>
-      <div class="metric">
-        <div class="metric-icon"><el-icon><Clock /></el-icon></div>
-        <div class="metric-body">
-          <span>当前页分时规则</span>
-          <div class="metric-value"><strong>{{ timedCount }}</strong><small>条</small></div>
-        </div>
-      </div>
-    </div>
-
-    <el-card shadow="never" class="water-panel filter-card">
-      <template #header>
-        <div class="card-title">
-          <el-icon><Search /></el-icon>
-          <span>阈值筛选</span>
-        </div>
-      </template>
-      <el-form :inline="true" :model="query" class="filter-form">
-        <el-form-item label="设备编码">
-          <el-input v-model="query.deviceCode" clearable placeholder="请输入供水设备编码" />
-        </el-form-item>
-        <el-form-item label="预警分类">
-          <el-select v-model="query.warningType" clearable filterable placeholder="展示全部" class="warning-select">
-            <el-option v-for="item in warningTypes" :key="item" :label="item" :value="item" />
-          </el-select>
-        </el-form-item>
-        <el-form-item>
-          <el-button type="primary" :icon="Search" @click="handleSearch">查询</el-button>
-          <el-button @click="resetQuery">展示全部</el-button>
-        </el-form-item>
-      </el-form>
-    </el-card>
-
-    <el-alert
-      v-if="loadError"
-      class="load-error"
-      type="error"
-      show-icon
-      :closable="false"
-      title="阈值加载失败"
-      :description="loadError"
-    >
-      <el-button size="small" type="primary" :loading="loading" @click="loadRules">重试</el-button>
-    </el-alert>
-
-    <el-card shadow="never" class="water-panel table-card">
-      <template #header>
-        <div class="card-title">
-          <el-icon><DataLine /></el-icon>
-          <span>阈值配置清单</span>
-        </div>
-      </template>
-      <el-table :data="page.records" stripe border v-loading="loading">
-        <el-table-column type="index" label="#" width="55" />
-        <el-table-column prop="deviceCode" label="供水设备编码" min-width="190" show-overflow-tooltip />
-        <el-table-column prop="warningType" label="预警分类" min-width="120">
-          <template #default="{ row }">
-            <el-tag effect="light">{{ row.warningType }}</el-tag>
-          </template>
-        </el-table-column>
-        <el-table-column prop="warningCode" label="预警编码" min-width="150" show-overflow-tooltip />
-        <el-table-column label="阈值范围" min-width="160">
-          <template #default="{ row }">
-            <span class="threshold-range">{{ thresholdText(row) }}</span>
-          </template>
-        </el-table-column>
-        <el-table-column label="生效时段" min-width="140">
-          <template #default="{ row }">
-            <el-tag :type="isTimed(row) ? 'warning' : 'success'" effect="plain">
-              {{ periodText(row) }}
-            </el-tag>
-          </template>
-        </el-table-column>
-        <el-table-column prop="updateTime" label="更新时间" min-width="170" show-overflow-tooltip />
-        <el-table-column label="操作" width="150" fixed="right">
-          <template #default="{ row }">
-            <el-button link type="primary" :icon="Edit" @click="openEdit(row)">编辑</el-button>
-            <el-button
-              link
-              type="danger"
-              :icon="Delete"
-              :loading="deletingId === row.id"
-              :disabled="deletingId !== null && deletingId !== row.id"
-              @click="handleDelete(row)"
-            >删除</el-button>
-          </template>
-        </el-table-column>
-      </el-table>
-      <el-pagination
-        v-model:current-page="query.pageNum"
-        v-model:page-size="query.pageSize"
-        :total="page.total"
-        layout="total, sizes, prev, pager, next"
-        :page-sizes="[10, 20, 50]"
-        @change="loadRules"
-      />
-    </el-card>
-
-    <el-drawer
-      v-model="drawerVisible"
-      size="520px"
-      direction="rtl"
-      class="detail-drawer threshold-drawer"
-      :title="form.id ? '编辑阈值' : '新增阈值'"
-    >
-      <el-alert
-        v-if="deviceLoadError"
-        class="load-error"
-        type="error"
-        show-icon
-        :closable="false"
-        title="供水设备加载失败"
-        :description="deviceLoadError"
-      >
-        <el-button size="small" type="primary" @click="loadDevices">重试</el-button>
-      </el-alert>
-      <el-form :model="form" label-width="100px">
-        <el-form-item label="监测设备">
-          <el-select
-            v-if="!form.id"
-            v-model="form.deviceCodes"
-            multiple
-            filterable
-            placeholder="请选择供水监测设备"
-          >
-            <el-option
-              v-for="device in devices"
-              :key="device.equipmentId || device.id"
-              :label="device.equipmentName ? `${device.equipmentName}(${device.equipmentCode})` : device.equipmentCode"
-              :value="device.equipmentCode"
-            />
-          </el-select>
-          <el-select v-else v-model="form.deviceCode" filterable placeholder="请选择供水监测设备">
-            <el-option
-              v-for="device in devices"
-              :key="device.equipmentId || device.id"
-              :label="device.equipmentName ? `${device.equipmentName}(${device.equipmentCode})` : device.equipmentCode"
-              :value="device.equipmentCode"
-            />
-          </el-select>
-        </el-form-item>
-        <el-form-item label="预警分类">
-          <el-select v-model="form.warningType" filterable allow-create placeholder="请选择预警分类">
-            <el-option v-for="item in warningTypes" :key="item" :label="item" :value="item" />
-          </el-select>
-        </el-form-item>
-        <el-form-item label="预警编码">
-          <el-input v-model="form.warningCode" placeholder="例如 WARN-T5-FLOW" />
-        </el-form-item>
-        <el-form-item label="阈值下限">
-          <el-input-number v-model="form.minValue" :precision="3" placeholder="可留空" />
-        </el-form-item>
-        <el-form-item label="阈值上限">
-          <el-input-number v-model="form.maxValue" :precision="3" placeholder="可留空" />
-        </el-form-item>
-        <el-form-item label="分时生效">
-          <div class="period-range">
-            <el-time-picker v-model="form.periodStart" format="HH:mm" value-format="HH:mm" placeholder="全天开始" />
-            <span>至</span>
-            <el-time-picker v-model="form.periodEnd" format="HH:mm" value-format="HH:mm" placeholder="全天结束" />
-          </div>
-        </el-form-item>
-        <el-form-item label="备注">
-          <el-input v-model="form.remark" type="textarea" :rows="3" maxlength="200" show-word-limit />
-        </el-form-item>
-      </el-form>
-      <template #footer>
-        <el-button @click="drawerVisible = false">取消</el-button>
-        <el-button type="primary" :loading="saving" @click="saveThreshold">保存</el-button>
-      </template>
-    </el-drawer>
-  </div>
-</template>
-
-<script setup>
-import { computed, onMounted, ref } from 'vue'
-import { ElMessage, ElMessageBox } from 'element-plus'
-import { Bell, CircleCheck, Clock, Connection, DataLine, Delete, Edit, Plus, Refresh, Search, Warning } from '@element-plus/icons-vue'
-import { deleteThresholds, listThresholdDevices, listThresholds, saveThresholds, updateThreshold } from '@/api/pipeNetwork/waterSupply'
-import { normalizePage, unwrapResponse } from '@/utils/waterSupplyModel'
-import { buildThresholdQuery, buildThresholdSavePayload, buildThresholdUpdatePayload } from '@/utils/waterSupplyUiModel'
-import { getRequestErrorMessage } from '@/utils/waterFacilityQuery'
-
-const warningTypes = ['压力预警', '流量预警', '漏失预警', '设备离线', '水质异常']
-const query = ref({ pageNum: 1, pageSize: 10, deviceCode: '', warningType: undefined })
-const page = ref({ records: [], total: 0 })
-const devices = ref([])
-const loading = ref(false)
-const saving = ref(false)
-const deletingId = ref(null)
-const loadError = ref('')
-const deviceLoadError = ref('')
-const drawerVisible = ref(false)
-const form = ref({})
-
-const allDayCount = computed(() => page.value.records.filter(item => !isTimed(item)).length)
-const timedCount = computed(() => page.value.records.filter(isTimed).length)
-
-const thresholdText = row => row.minValue != null || row.maxValue != null
-  ? `${row.minValue ?? '-'} ~ ${row.maxValue ?? '-'}`
-  : '-'
-const isTimed = row => Boolean(row.periodStart || row.periodEnd)
-const periodText = row => isTimed(row)
-  ? `${row.periodStart || '--:--'} - ${row.periodEnd || '--:--'}`
-  : '全天'
-
-async function loadRules() {
-  loading.value = true
-  try {
-    page.value = normalizePage(await listThresholds(buildThresholdQuery(query.value)))
-    loadError.value = ''
-  } catch (error) {
-    loadError.value = getRequestErrorMessage(error, '请检查服务状态')
-  } finally {
-    loading.value = false
-  }
-}
-
-async function loadDevices() {
-  try {
-    const response = unwrapResponse(await listThresholdDevices())
-    devices.value = Array.isArray(response) ? response : []
-    deviceLoadError.value = ''
-  } catch (error) {
-    deviceLoadError.value = getRequestErrorMessage(error, '请检查服务状态')
-  }
-}
-
-function handleSearch() {
-  query.value.pageNum = 1
-  loadRules()
-}
-
-function resetQuery() {
-  query.value = { pageNum: 1, pageSize: 10, deviceCode: '', warningType: undefined }
-  loadRules()
-}
-
-function openCreate() {
-  form.value = {
-    deviceCodes: [],
-    warningType: '压力预警',
-    warningCode: '',
-    minValue: undefined,
-    maxValue: undefined,
-    periodStart: '',
-    periodEnd: '',
-    remark: ''
-  }
-  drawerVisible.value = true
-}
-
-function openEdit(row) {
-  form.value = { ...row }
-  drawerVisible.value = true
-}
-
-function validateForm() {
-  const current = form.value
-  const selectedDevices = current.id ? [current.deviceCode] : current.deviceCodes
-  if (!selectedDevices?.filter(Boolean).length) return '请选择至少一个供水监测设备'
-  if (!current.warningType?.trim()) return '请选择预警分类'
-  if (!current.warningCode?.trim()) return '请填写预警编码'
-  if (current.minValue == null && current.maxValue == null) return '阈值下限和上限至少填写一项'
-  if (current.minValue != null && current.maxValue != null && Number(current.minValue) > Number(current.maxValue)) return '阈值下限不能大于上限'
-  if (Boolean(current.periodStart) !== Boolean(current.periodEnd)) return '分时开始和结束时间必须同时填写'
-  return ''
-}
-
-async function saveThreshold() {
-  const message = validateForm()
-  if (message) {
-    ElMessage.warning(message)
-    return
-  }
-  saving.value = true
-  try {
-    if (form.value.id) {
-      await updateThreshold(buildThresholdUpdatePayload(form.value))
-    } else {
-      await saveThresholds(buildThresholdSavePayload(form.value))
-    }
-    ElMessage.success('阈值已保存')
-    drawerVisible.value = false
-    await loadRules()
-  } catch (error) {
-    ElMessage.error(getRequestErrorMessage(error, '阈值保存失败,请检查权限或服务状态'))
-  } finally {
-    saving.value = false
-  }
-}
-
-async function handleDelete(row) {
-  try {
-    await ElMessageBox.confirm(`确认删除设备 ${row.deviceCode} 的阈值规则吗?`, '删除确认', { type: 'warning' })
-    deletingId.value = row.id
-    await deleteThresholds([row.id])
-    ElMessage.success('阈值已删除')
-    await loadRules()
-  } catch (error) {
-    if (error !== 'cancel') {
-      ElMessage.error(getRequestErrorMessage(error, '阈值删除失败,请检查权限或服务状态'))
-    }
-  } finally {
-    deletingId.value = null
-  }
-}
-
-onMounted(() => {
-  loadRules()
-  loadDevices()
-})
-</script>
-
-<style lang="scss">
-@use './waterSupplyPageTheme.scss';
-</style>
-
-<style scoped>
-.threshold-page {
-  background: linear-gradient(135deg, #eff6ff 0%, #f8fafc 45%, #eef2f7 100%);
-}
-
-.heading-actions {
-  display: flex;
-  align-items: center;
-  gap: 10px;
-}
-
-.page-title-icon {
-  display: grid;
-  place-items: center;
-  width: 42px;
-  height: 42px;
-  color: #fff;
-  background: linear-gradient(135deg, #2563eb 0%, #06b6d4 100%);
-  border-radius: 8px;
-  box-shadow: 0 8px 16px rgba(37, 99, 235, 0.2);
-}
-
-.metric-grid {
-  grid-template-columns: repeat(4, minmax(0, 1fr));
-}
-
-.metric {
-  display: flex;
-  align-items: center;
-  gap: 14px;
-  min-height: 78px;
-  background: #fff;
-  border: 1px solid #e2e8f0;
-  border-radius: 8px;
-  box-shadow: 0 6px 14px rgba(15, 23, 42, 0.04);
-}
-
-.metric-icon {
-  position: static;
-  flex: 0 0 42px;
-  display: grid;
-  place-items: center;
-  width: 42px;
-  height: 42px;
-  color: #2563eb;
-  background: linear-gradient(135deg, #dbeafe 0%, #eff6ff 100%);
-  border-radius: 8px;
-}
-
-.metric-icon :deep(.el-icon) {
-  font-size: 22px;
-}
-
-.metric-body span {
-  display: block;
-  margin-bottom: 3px;
-  overflow: hidden;
-  color: #64748b;
-  font-size: 13px;
-  text-overflow: ellipsis;
-  white-space: nowrap;
-}
-
-.metric-value {
-  display: flex;
-  align-items: baseline;
-  gap: 5px;
-}
-
-.metric-value strong {
-  color: #1e293b;
-  font-size: 24px;
-  line-height: 1.2;
-}
-
-.filter-card,
-.table-card {
-  margin-bottom: 16px;
-  border: 1px solid #e2e8f0;
-  border-radius: 8px;
-  box-shadow: 0 6px 14px rgba(15, 23, 42, 0.04);
-}
-
-.card-title {
-  display: flex;
-  align-items: center;
-  gap: 8px;
-  color: #1e293b;
-  font-weight: 600;
-}
-
-.card-title :deep(.el-icon) {
-  color: #2563eb;
-  font-size: 18px;
-}
-
-.filter-form :deep(.el-form-item) {
-  margin-bottom: 0;
-}
-
-.warning-select {
-  width: 220px;
-}
-
-.load-error {
-  margin-bottom: 16px;
-}
-
-.threshold-range {
-  color: #17324d;
-  font-weight: 600;
-}
-
-.table-card :deep(.el-card__body) {
-  padding-top: 0;
-}
-
-.table-card :deep(.el-table__header th) {
-  color: #475569;
-  background: #f8fafc;
-}
-
-.table-card :deep(.el-pagination) {
-  justify-content: flex-end;
-  margin-top: 16px;
-}
-
-.threshold-drawer :deep(.el-select),
-.threshold-drawer :deep(.el-input-number) {
-  width: 100%;
-}
-
-.period-range {
-  display: flex;
-  align-items: center;
-  gap: 8px;
-  width: 100%;
-}
-
-.period-range :deep(.el-date-editor) {
-  flex: 1;
-}
-
-@media (max-width: 1000px) {
-  .metric-grid {
-    grid-template-columns: repeat(2, minmax(0, 1fr));
-  }
-}
-
-@media (max-width: 650px) {
-  .page-heading,
-  .heading-actions {
-    align-items: stretch;
-    flex-direction: column;
-  }
-
-  .metric-grid {
-    grid-template-columns: 1fr;
-  }
-
-  .threshold-drawer {
-    width: 100% !important;
-  }
-}
-</style>

+ 0 - 1
src/views/subSystem/waterSupply/monitorAlarm/threshold.vue

@@ -1 +0,0 @@
-<script setup>import Page from '../wjcbj/yzgl.vue'</script><template><Page /></template>

+ 0 - 1
src/views/subSystem/waterSupply/wjcbj/yzgl.vue

@@ -1 +0,0 @@
-<script setup>import WaterThresholdPage from '../components/WaterThresholdPage.vue'</script><template><WaterThresholdPage /></template>

+ 0 - 98
tests/waterSupplyThreshold.test.mjs

@@ -1,98 +0,0 @@
-import test from 'node:test'
-import assert from 'node:assert/strict'
-import { readFile } from 'node:fs/promises'
-import {
-  buildThresholdQuery,
-  buildThresholdSavePayload,
-  buildThresholdUpdatePayload
-} from '../src/utils/waterSupplyUiModel.js'
-
-test('threshold page uses the water-only threshold device endpoint', async () => {
-  const source = await readFile(
-    'src/views/subSystem/waterSupply/components/WaterThresholdPage.vue',
-    'utf8'
-  )
-
-  assert.match(source, /listThresholdDevices/u)
-  assert.doesNotMatch(source, /listDeviceLedger/u)
-})
-
-test('threshold drawer follows the shared right-side detail drawer contract', async () => {
-  const source = await readFile(
-    'src/views/subSystem/waterSupply/components/WaterThresholdPage.vue',
-    'utf8'
-  )
-
-  assert.match(source, /<el-drawer\b[^>]*direction="rtl"[^>]*>/u)
-  assert.match(source, /<el-drawer\b[^>]*class="detail-drawer threshold-drawer"[^>]*>/u)
-  assert.match(source, /\.threshold-drawer\s*\{\s*width:\s*100% !important/u)
-})
-
-test('builds the flat batch-save contract expected by the threshold API', () => {
-  const payload = buildThresholdSavePayload({
-    id: 'draft-id',
-    deviceCodes: [' WS-FLOW-1 ', '', 'WS-FLOW-2', 'WS-FLOW-1'],
-    warningType: ' 流量预警 ',
-    warningCode: ' WARN-FLOW ',
-    minValue: '20',
-    maxValue: '40',
-    periodStart: '08:00',
-    periodEnd: '20:00',
-    remark: ' 高峰期阈值 '
-  })
-
-  assert.deepEqual(payload, {
-    deviceCodes: ['WS-FLOW-1', 'WS-FLOW-2'],
-    warningType: '流量预警',
-    warningCode: 'WARN-FLOW',
-    minValue: 20,
-    maxValue: 40,
-    periodStart: '08:00',
-    periodEnd: '20:00',
-    remark: '高峰期阈值'
-  })
-})
-
-test('builds an update payload that preserves the threshold id', () => {
-  const payload = buildThresholdUpdatePayload({
-    id: 'threshold-id',
-    deviceCode: 'WS-FLOW-1',
-    warningType: '流量预警',
-    warningCode: 'WARN-FLOW',
-    minValue: 20,
-    maxValue: 40,
-    periodStart: '08:00',
-    periodEnd: '20:00',
-    createTime: '2026-09-02 10:00:00'
-  })
-
-  assert.deepEqual(payload, {
-    id: 'threshold-id',
-    deviceCode: 'WS-FLOW-1',
-    warningType: '流量预警',
-    warningCode: 'WARN-FLOW',
-    minValue: 20,
-    maxValue: 40,
-    periodStart: '08:00',
-    periodEnd: '20:00'
-  })
-})
-
-test('builds threshold list filters without alarm-only fields', () => {
-  const query = buildThresholdQuery({
-    pageNum: 2,
-    pageSize: 20,
-    deviceCode: 'WS-FLOW-1',
-    warningType: '流量预警',
-    keyword: ' ',
-    alarmLevel: 2,
-    alarmStatus: 0
-  })
-
-  assert.deepEqual(query, {
-    pageNum: 2,
-    pageSize: 20,
-    deviceCode: 'WS-FLOW-1',
-    warningType: '流量预警'
-  })
-})

Některé soubory nejsou zobrazeny, neboť je v těchto rozdílových datech změněno mnoho souborů