Переглянути джерело

feat: 新增告警计划添加弹窗

liujia 1 місяць тому
батько
коміт
b220981

+ 5 - 0
components.d.ts

@@ -15,13 +15,16 @@ declare module 'vue' {
     ACascader: typeof import('ant-design-vue/es')['Cascader']
     ACheckbox: typeof import('ant-design-vue/es')['Checkbox']
     ACheckboxGroup: typeof import('ant-design-vue/es')['CheckboxGroup']
+    ACol: typeof import('ant-design-vue/es')['Col']
     ACollapse: typeof import('ant-design-vue/es')['Collapse']
     ACollapsePanel: typeof import('ant-design-vue/es')['CollapsePanel']
     AConfigProvider: typeof import('ant-design-vue/es')['ConfigProvider']
+    ADivider: typeof import('ant-design-vue/es')['Divider']
     ADrawer: typeof import('ant-design-vue/es')['Drawer']
     ADropdown: typeof import('ant-design-vue/es')['Dropdown']
     AForm: typeof import('ant-design-vue/es')['Form']
     AFormItem: typeof import('ant-design-vue/es')['FormItem']
+    AFormItemRest: typeof import('ant-design-vue/es')['FormItemRest']
     AInput: typeof import('ant-design-vue/es')['Input']
     AInputNumber: typeof import('ant-design-vue/es')['InputNumber']
     AInputPassword: typeof import('ant-design-vue/es')['InputPassword']
@@ -36,10 +39,12 @@ declare module 'vue' {
     APageHeader: typeof import('ant-design-vue/es')['PageHeader']
     APagination: typeof import('ant-design-vue/es')['Pagination']
     APopconfirm: typeof import('ant-design-vue/es')['Popconfirm']
+    AProgress: typeof import('ant-design-vue/es')['Progress']
     ARadio: typeof import('ant-design-vue/es')['Radio']
     ARadioButton: typeof import('ant-design-vue/es')['RadioButton']
     ARadioGroup: typeof import('ant-design-vue/es')['RadioGroup']
     ARangePicker: typeof import('ant-design-vue/es')['RangePicker']
+    ARow: typeof import('ant-design-vue/es')['Row']
     ASelect: typeof import('ant-design-vue/es')['Select']
     ASelectOption: typeof import('ant-design-vue/es')['SelectOption']
     ASkeleton: typeof import('ant-design-vue/es')['Skeleton']

+ 11 - 0
src/api/alarm/index.ts

@@ -0,0 +1,11 @@
+import request from '@/request'
+import type * as TYPE from './types'
+
+/**
+ * 告警计划保存
+ */
+export const saveAlarmPlan = (
+  params: TYPE.AlarmPlanParams
+): Promise<ResponseData<TYPE.AlarmPlanResponseData>> => {
+  return request.post('/alarm/plan/save', params)
+}

+ 73 - 0
src/api/alarm/types.ts

@@ -0,0 +1,73 @@
+/**
+ * 告警计划事件类型
+ * @use 以下3个的param参数传 {}
+ * @enum {string} stay_detection 停留事件
+ * @enum {string} retention_detection 异常滞留
+ * @enum {string} toileting_detection 如厕事件
+ *
+ * @use 以下3个的param参数传 { "start_time": "22:00", "end_time": "6:00" }
+ * @enum {string} toileting_frequency 如厕频次统计
+ * @enum {string} night_toileting_frequency 夜间如厕频次统计
+ * @enum {string} bathroom_stay_frequency 卫生间频次统计
+ *
+ * @use 以下2个的param参数传 { "start_time": "22:00", "end_time": "6:00", "count": 3 }
+ * @enum {string} toilet_frequency_abnormal 如厕频次异常
+ * @enum {string} night_toileting_frequency_abnormal 起夜异常
+ *
+ * @use 以下1个的param参数传 { "time_threshold": 300 }
+ * @enum {string} target_absence 异常消失
+ */
+type EventParamMap = {
+  stay_detection: object
+  retention_detection: object
+  toileting_detection: object
+  toileting_frequency: { start_time: string; end_time: string }
+  night_toileting_frequency: { start_time: string; end_time: string }
+  bathroom_stay_frequency: { start_time: string; end_time: string }
+  toilet_frequency_abnormal: { start_time: string; end_time: string; count: number }
+  night_toileting_frequency_abnormal: { start_time: string; end_time: string; count: number }
+  target_absence: { time_threshold: number }
+}
+
+export type EventVal = keyof EventParamMap
+export type EventValParam<T extends EventVal = EventVal> = EventParamMap[T]
+
+type TimeRange = { start_time: string; end_time: string }
+interface AlarmTimePlan {
+  startDate: string // 开始时间
+  stopDate: string // 结束时间
+  weekdays?: string[] // 每周的生效日期[1,2,3,6,7]
+  monthDays?: string[] // 每月的生效日期[1,2,3,15,20,25,26,27]
+  timeRange: TimeRange[] // 	每日生效时间[{"start_time": "00:00","end_time": "12:00"},{"start_time": "18:00","end_time": "23:59"}, ...]
+}
+/**
+ * 保存告警计划请求参数
+ * @see http://8.130.28.21:31090/doc.html#/%E9%97%A8%E6%88%B7%E6%9C%8D%E5%8A%A1/web%E7%AB%AF%E5%91%8A%E8%AD%A6%E7%9B%B8%E5%85%B3/savePlan
+ * @param clientId 设备ID
+ * @param name 告警计划名称
+ * @param region 检测区域 [left, top, width, height]
+ * @param eventVal 告警事件值
+ * @param param 告警参数 需要根据 eventVal 传对应的参数
+ * @param alarmTimePlan 告警时间计划
+ * @param enable 是否启用(number) 1: 启用 0: 禁用
+ */
+export interface AlarmPlanParams {
+  clientId: string // 设备ID
+  name: string // 告警计划名称
+  region: string[] // 检测区域 [left, top, width, height]
+  eventVal: EventVal | null // 告警事件值
+  param: EventValParam | object // 告警参数 需要根据 eventVal 传对应的参数
+  alarmTimePlan: AlarmTimePlan // 告警时间计划
+  enable: 0 | 1 // 是否启用(number) 1: 启用 0: 禁用
+}
+
+/**
+ * 保存告警计划响应数据
+ */
+export interface AlarmPlanResponseData extends Omit<AlarmPlanParams, 'eventVal'> {
+  eventVal: EventVal // 告警事件值
+  id: number // 告警计划ID
+  uuid: string // 告警计划UUID
+  createTime: string // 创建时间
+  updateTime: string // 更新时间
+}

+ 90 - 33
src/views/device/detail/components/alarmPlanModal/index.vue

@@ -62,19 +62,21 @@
             @change="effectTypeChange"
           >
             <a-radio value="week">按周</a-radio>
-            <a-radio value="mouth">按月</a-radio>
+            <a-radio value="month">按月</a-radio>
           </a-radio-group>
         </a-form-item>
 
         <a-form-item label="生效范围" name="firmwareVersion">
-          <a-checkbox
-            v-model:checked="checkState.checkAll"
-            :indeterminate="checkState.indeterminate"
-            @change="onCheckAllChange"
-          >
-            全选
-          </a-checkbox>
-          <a-checkbox-group v-model:value="formState.effectTimeRanges" :options="plainOptions" />
+          <a-form-item-rest>
+            <a-checkbox
+              v-model:checked="checkState.checkAll"
+              :indeterminate="checkState.indeterminate"
+              @change="onCheckAllChange"
+            >
+              全选
+            </a-checkbox>
+            <a-checkbox-group v-model:value="formState.effectTimeRanges" :options="plainOptions" />
+          </a-form-item-rest>
         </a-form-item>
 
         <a-form-item label="生效时段">
@@ -87,8 +89,8 @@
             <a-button size="small" type="link" @click="addEffectTime">添加</a-button>
           </div>
           <div style="margin-top: 12px">
-            <span v-if="!formState.effectTimeFrames.length" style="color: #aaa; font-size: 12px"
-              >暂无生效时段</span
+            <span v-if="!formState.effectTimeFrames.length" style="color: #aaa; font-size: 14px"
+              >⚠️暂无生效时段</span
             >
             <a-space wrap v-else>
               <a-tag
@@ -120,8 +122,8 @@
 <script setup lang="ts">
 import { ref, reactive, watch } from 'vue'
 import { message, type FormInstance } from 'ant-design-vue'
-// import * as deviceApi from '@/api/device'
-// import type { Rule } from 'ant-design-vue/es/form'
+import * as alarmApi from '@/api/alarm'
+import type { EventVal, EventValParam } from '@/api/alarm/types'
 
 defineOptions({
   name: 'AlarmPlanModal',
@@ -181,7 +183,7 @@ const mouthOptions = [
 type FormState = {
   planName: string // 计划名称
   region: string[] // 检测区域 [left, top, width, height]
-  eventType: string | null // 事件类型
+  eventType: EventVal | null // 事件类型
   planTime: string[] // 计划时间
   effectType: 'week' | 'month' // 生效方式 周week、月month
   effectTimeRanges: string[] // 生效范围 周 1-7、月 1-31
@@ -193,7 +195,7 @@ type FormState = {
 const formState = reactive<FormState>({
   planName: '',
   region: [],
-  eventType: '',
+  eventType: null,
   planTime: [],
   effectType: 'week',
   effectTimeRanges: weekOptions,
@@ -269,6 +271,17 @@ const cancel = () => {
   emit('update:open', false)
 }
 
+// 星期映射数字
+const weekdaysReverseMap: Record<string, number> = {
+  周一: 1,
+  周二: 2,
+  周三: 3,
+  周四: 4,
+  周五: 5,
+  周六: 6,
+  周日: 7,
+}
+
 const submitLoading = ref(false)
 // 确定
 const submit = () => {
@@ -276,24 +289,68 @@ const submit = () => {
     ?.validate()
     .then(() => {
       console.log('校验通过', formState)
-      // submitLoading.value = true
-      // deviceApi
-      //   .addDevice({
-      //     clientId: formState.deviceId,
-      //     devType: formState.deviceType,
-      //     software: formState.firmwareVersion,
-      //     tenantId: formState.tenantId,
-      //   })
-      //   .then((res) => {
-      //     console.log('添加成功', res)
-      //     submitLoading.value = false
-      //     message.success('添加成功')
-      //     emit('success')
-      //     cancel()
-      //   })
-      //   .catch(() => {
-      //     submitLoading.value = false
-      //   })
+      let paramData: EventValParam = {}
+      if (
+        ['stay_detection', 'retention_detection', 'toileting_detection'].includes(
+          formState.eventType as string
+        )
+      ) {
+        console.log('参数传 {}')
+        paramData = {}
+      } else if (
+        ['toileting_frequency', 'night_toileting_frequency', 'bathroom_stay_frequency'].includes(
+          formState.eventType as string
+        )
+      ) {
+        console.log('参数传 { "start_time": "22:00", "end_time": "6:00" }')
+        paramData = { start_time: '22:00', end_time: '6:00' }
+      } else if (
+        ['toilet_frequency_abnormal', 'night_toileting_frequency_abnormal'].includes(
+          formState.eventType as string
+        )
+      ) {
+        console.log('参数传 { "start_time": "22:00", "end_time": "6:00", "count": 3 }')
+        paramData = { start_time: '22:00', end_time: '6:00', count: 3 }
+      } else if (['target_absence'].includes(formState.eventType as string)) {
+        console.log('参数传 { "time_threshold": 300 }')
+        paramData = { time_threshold: 300 }
+      }
+
+      const params = {
+        clientId: '', // 设备ID
+        name: formState.planName, // 计划名称
+        region: formState.region, // 检测区域 [left, top, width, height]
+        eventVal: formState.eventType, // 事件类型 与 param 有联动关系
+        param: paramData, // 事件参数 与 eventVal 有联动关系
+        enable: Number(formState.isEnable) as 0 | 1, // 是否启用 0否 1是
+        alarmTimePlan: {
+          startDate: formState.planTime[0], // 计划开始时间
+          stopDate: formState.planTime[1], // 计划结束时间
+          monthDays: formState.effectType === 'month' ? formState.effectTimeRanges : [], // 按月生效范围 1-31
+          weekdays:
+            formState.effectType === 'week'
+              ? formState.effectTimeRanges.map((item: string) => String(weekdaysReverseMap[item]))
+              : [], // 按周生效范围 1-7
+          timeRange: formState.effectTimeFrames.map((item) => ({
+            start_time: item.startTime, // 生效开始时间
+            end_time: item.endTime, // 生效结束时间
+          })),
+        },
+      }
+      console.log('🚀🚀🚀提交参数', params)
+      submitLoading.value = true
+      alarmApi
+        .saveAlarmPlan(params)
+        .then((res) => {
+          console.log('添加成功', res)
+          submitLoading.value = false
+          message.success('添加成功')
+          emit('success')
+          cancel()
+        })
+        .catch(() => {
+          submitLoading.value = false
+        })
     })
     .catch((err) => {
       console.log('校验失败', err)