Bläddra i källkod

feat(water): deliver threshold management (#9)

Kazerin 3 dagar sedan
förälder
incheckning
eba9ab1cbf

+ 1 - 0
memory/index.md

@@ -54,3 +54,4 @@ npm run dev       # 默认 http://localhost:80
 - [2026-08-21:工程技能仓库配置](sessions/2026-08-21.md)
 - [2026-08-22:供水报警与工单前端适配设计](sessions/2026-08-22.md)
 - [2026-09-01:供水管网数据统计分析 T3](sessions/2026-09-01.md)
+- [2026-09-02:阈值管理 T5](sessions/2026-09-02.md)

+ 9 - 0
memory/sessions/2026-09-02.md

@@ -0,0 +1,9 @@
+# 2026-09-02 会话记录
+
+## T5 阈值管理
+
+- 目标:完成 Gogs Issue #9“[供水] T5 阈值管理”的前端部分,覆盖设备/分类筛选、批量设备选择、分时表单、编辑、删除、加载与错误反馈。
+- 实现:`WaterAlarmPage.vue` 的阈值模式改为独立筛选与批量配置表单;新增阈值保存、更新和查询契约构建函数,修复原先向后端发送 `{deviceCodes, thresholds}` 嵌套结构导致的保存失败。
+- 契约:批量保存发送平铺 `deviceCodes/warningType/warningCode/minValue/maxValue/periodStart/periodEnd/remark`;更新保留 `id` 并发送平铺字段;查询只发送阈值接口支持的参数。
+- 验证:`node --test tests/waterSupplyThreshold.test.mjs tests/waterSupplyUiModel.test.mjs tests/waterSupplyModel.test.mjs` 7/7 通过;`npm run build:prod` 通过,仅存在既有 Sass 弃用警告。
+- 边界:未修改其他页面;全量 Node 测试中 `src/views/monitor/job/index.vue` 的既有详情弹窗规则失败,属于工作区既有非 T5 改动。

+ 64 - 0
src/utils/waterSupplyUiModel.js

@@ -51,3 +51,67 @@ export function buildMaintainOrderPayload(device = {}, form = {}) {
 export function getDeviceOrderEndpoint(view) {
   return view === 'dispatch' ? '/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
+  })
+}

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 0 - 1
src/views/subSystem/waterSupply/components/WaterAlarmPage.vue


+ 76 - 0
tests/waterSupplyThreshold.test.mjs

@@ -0,0 +1,76 @@
+import test from 'node:test'
+import assert from 'node:assert/strict'
+import {
+  buildThresholdQuery,
+  buildThresholdSavePayload,
+  buildThresholdUpdatePayload
+} from '../src/utils/waterSupplyUiModel.js'
+
+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: '流量预警'
+  })
+})

Vissa filer visades inte eftersom för många filer har ändrats