Эх сурвалжийг харах

perf(views): 初步完善“报警管理”模块

wangshun 1 сар өмнө
parent
commit
aa05b42d8b

+ 307 - 11
src/views/alarm-manage/AlarmManage.vue

@@ -1,24 +1,92 @@
 <script setup lang="ts">
-import { ref, useTemplateRef } from 'vue';
-import dayjs from 'dayjs';
+import { onMounted, ref, useTemplateRef } from 'vue';
+import { message } from 'ant-design-vue';
+import dayjs, { Dayjs } from 'dayjs';
 
 import ConfirmModal from '@/components/ConfirmModal.vue';
 import SvgIcon from '@/components/SvgIcon.vue';
+import { useDictData } from '@/hooks/dict-data';
+import { useRequest } from '@/hooks/request';
 import { t } from '@/i18n';
+import { addAlarmEvent } from '@/api';
+import { DictCode } from '@/constants';
 
 import AlarmConditions from './AlarmConditions.vue';
+import AlarmExecution from './AlarmExecution.vue';
 
-import type { Rule } from 'ant-design-vue/es/form';
-import type { AlarmForm } from '@/types';
+import type { FormInstance, Rule } from 'ant-design-vue/es/form';
+import type { AlarmEventItem, AlarmForm, ExecutionAction, TriggerConditionItem } from '@/types';
+
+const { handleRequest } = useRequest();
+const { dictData: alarmCondition, getDictData: getAlarmCondition } = useDictData(DictCode.AlarmCondition);
+const { dictData: executionAction, getDictData: getExecutionAction } = useDictData(DictCode.ExecutionAction);
+const { dictData: alarmRepeatTime, getDictData: getAlarmRepeatTime } = useDictData(DictCode.AlarmRepeatTime);
+const { dictData: alarmAlgOperator, getDictData: getAlarmAlgOperator } = useDictData(DictCode.AlarmAlgOperator);
+const { dictData: alarmDeviceState, getDictData: getAlarmDeviceState } = useDictData(DictCode.AlarmDeviceState);
+const { dictData: alarmNotifyMethod, getDictData: getAlarmNotifyMethod } = useDictData(DictCode.AlarmNotifyMethod);
 
 const modalComponentRef = useTemplateRef('modalComponent');
+const formRef = ref<FormInstance>();
+const triggerConditionRefs = ref<InstanceType<typeof AlarmConditions>[]>([]);
+const judgmentConditionRefs = ref<InstanceType<typeof AlarmConditions>[]>([]);
+const executionActionRefs = ref<InstanceType<typeof AlarmConditions>[]>([]);
+
+const triggerConditionList = ref<TriggerConditionItem[]>([
+  {
+    type: 0,
+    subType: '',
+    groupId: undefined,
+    childGroupId: undefined,
+    deviceId: undefined,
+    paramCode: '',
+    alarmAlgOperator: '',
+    alarmThreshold: '',
+    alarmDeviceState: '',
+    alarmScheduledTime: undefined,
+    alarmRepeatTime: '',
+  },
+]);
+
+const judgmentConditionList = ref<TriggerConditionItem[]>([
+  {
+    type: 1,
+    subType: '',
+    groupId: undefined,
+    childGroupId: undefined,
+    deviceId: undefined,
+    paramCode: '',
+    alarmAlgOperator: '',
+    alarmThreshold: '',
+    alarmDeviceState: '',
+    alarmScheduledTime: undefined,
+    alarmRepeatTime: '',
+  },
+]);
+
+const executionActionList = ref<ExecutionAction[]>([
+  {
+    type: 2,
+    subType: '',
+    groupId: undefined,
+    childGroupId: undefined,
+    deviceId: undefined,
+    paramCode: '',
+    alarmAdjustmentValue: '',
+    alarmContact: undefined,
+    alarmAlertContent: '',
+    alarmWaitTime: undefined,
+    alarmNotifyMethod: '',
+  },
+]);
 const historyOpen = ref<boolean>(false);
 const alarmOpen = ref<boolean>(false);
 // 日期时间格式
 const dateFormat = 'HH:mm';
 const alarmForm = ref<AlarmForm>({
-  name: '',
+  eventName: '',
   time: [dayjs(), dayjs()],
+  conditionLogic: 0,
+  enabled: true,
 });
 const historyData = ref([
   {
@@ -79,7 +147,7 @@ const alarmQuery = ref({
 });
 
 const rules: Record<string, Rule[]> = {
-  name: [{ required: true, message: t('common.cannotEmpty'), trigger: 'change' }],
+  eventName: [{ required: true, message: t('common.cannotEmpty'), trigger: 'change' }],
   time: [{ required: true, message: t('common.cannotEmpty'), trigger: 'change' }],
 };
 const historyColumns = [
@@ -194,7 +262,143 @@ const confirm = () => {
 };
 const switchPages = () => {};
 const switchHistoryPages = () => {};
-const addTriggerConditions = () => {};
+const addTriggerConditions = () => {
+  triggerConditionList.value.push({
+    type: 0,
+    subType: '',
+    groupId: undefined,
+    childGroupId: undefined,
+    deviceId: undefined,
+    paramCode: '',
+    alarmAlgOperator: '',
+    alarmThreshold: '',
+    alarmDeviceState: '',
+    alarmScheduledTime: undefined,
+    alarmRepeatTime: '',
+  });
+};
+
+const addJudgmentConditions = () => {
+  judgmentConditionList.value.push({
+    type: 1,
+    subType: '',
+    groupId: undefined,
+    childGroupId: undefined,
+    deviceId: undefined,
+    paramCode: '',
+    alarmAlgOperator: '',
+    alarmThreshold: '',
+    alarmDeviceState: '',
+    alarmScheduledTime: undefined,
+    alarmRepeatTime: '',
+  });
+};
+
+const addExecutionConditions = () => {
+  executionActionList.value.push({
+    type: 2,
+    subType: '',
+    groupId: undefined,
+    childGroupId: undefined,
+    deviceId: undefined,
+    paramCode: '',
+    alarmAdjustmentValue: '',
+    alarmContact: undefined,
+    alarmAlertContent: '',
+    alarmWaitTime: undefined,
+    alarmNotifyMethod: '',
+  });
+};
+
+// 添加格式转换
+const formatDate = (date: Dayjs) => date.format('HH:mm');
+
+const deleteTriggerClick = (index: number) => {
+  if (index === 0) {
+    return message.warning('触发条件不能为空!');
+  }
+  triggerConditionList.value.splice(index, 1);
+};
+
+const deleteJudgmentClick = (index: number) => {
+  judgmentConditionList.value.splice(index, 1);
+};
+
+const deleteExecutionClick = (index: number) => {
+  if (index === 0) {
+    return message.warning('执行动作不能为空!');
+  }
+  executionActionList.value.splice(index, 1);
+};
+
+const convertAtoB = (arr: TriggerConditionItem[] | ExecutionAction[]): AlarmEventItem[] => {
+  return arr.map((item) => {
+    // 解构固定字段和剩余属性
+    const { type, subType, groupId, childGroupId, deviceId, paramCode, ...dynamicFields } = item;
+
+    // 转换动态字段到 dataList
+    const dataList = Object.entries(dynamicFields)
+      .map(([code, value]) => ({
+        code,
+        value: String(value), // 确保值转为字符串
+      })) // 过滤空值(空字符串、null、undefined)
+      .filter(({ value }) => value !== '' && value != null && value != 'undefined');
+
+    // 返回符合 AlarmEventItem 结构的对象
+    return {
+      type,
+      subType,
+      groupId,
+      childGroupId,
+      deviceId,
+      paramCode,
+      dataList,
+    };
+  });
+};
+
+const okConfirm = async () => {
+  try {
+    const allComponents = [...triggerConditionRefs.value, ...judgmentConditionRefs.value, ...executionActionRefs.value];
+    await Promise.all(allComponents.map((child) => child.formRefSubmit()));
+
+    formRef.value
+      ?.validate()
+      .then(() => {
+        handleRequest(async () => {
+          const { eventName, time, enabled, conditionLogic } = alarmForm.value;
+          const data = time.map(formatDate) || [];
+          const triggerList = convertAtoB(triggerConditionList.value);
+          const judgmentList = convertAtoB(judgmentConditionList.value);
+          const executionList = convertAtoB(executionActionList.value);
+          await addAlarmEvent({
+            eventName,
+            startTime: data[0],
+            endTime: data[1],
+            enabled,
+            conditionLogic: conditionLogic === 1 ? true : false,
+            triggerConditionList: triggerList,
+            judgmentConditionList: judgmentList,
+            executionActionList: executionList,
+          });
+          alarmOpen.value = false;
+        });
+      })
+      .catch(() => {});
+  } catch {
+    console.log('存在验证未通过的表单');
+  }
+};
+onMounted(() => {
+  handleRequest(async () => {
+    await getAlarmCondition();
+    await getExecutionAction();
+    await getAlarmRepeatTime();
+    await getAlarmAlgOperator();
+    await getAlarmDeviceState();
+    await getAlarmNotifyMethod();
+  });
+});
 </script>
 
 <template>
@@ -252,10 +456,10 @@ const addTriggerConditions = () => {};
         />
       </AFlex>
     </div>
-    <AModal v-model:open="alarmOpen" title="预警条件添加" width="920px" :mask-closable="false">
+    <AModal v-model:open="alarmOpen" title="预警条件添加" width="920px" :mask-closable="false" @ok="okConfirm">
       <AForm ref="formRef" class="alarm-modal" :model="alarmForm" label-align="left" layout="vertical" :rules="rules">
-        <AFormItem label="事件名称" name="name">
-          <AInput v-model:value="alarmForm.name" placeholder="请输入" class="query-input query-bottom" />
+        <AFormItem label="事件名称" name="eventName">
+          <AInput v-model:value="alarmForm.eventName" placeholder="请输入" class="query-input query-bottom" />
         </AFormItem>
         <AFormItem label="生效时间段" name="time">
           <ATimeRangePicker
@@ -266,7 +470,19 @@ const addTriggerConditions = () => {};
           />
         </AFormItem>
         <div class="alarm-text">当任一情况发生</div>
-        <AlarmConditions :alarm-form="alarmForm" />
+
+        <div v-for="(item, index) in triggerConditionList" :key="index">
+          <AlarmConditions
+            ref="triggerConditionRefs"
+            @deleteClick="deleteTriggerClick"
+            :index="index"
+            :form="item"
+            :alarm-condition="alarmCondition"
+            :alarm-repeat-time="alarmRepeatTime"
+            :alarm-alg-operator="alarmAlgOperator"
+            :alarm-device-state="alarmDeviceState"
+          />
+        </div>
 
         <AButton type="primary" ghost class="icon-button button-top" @click="addTriggerConditions">
           <AFlex align="center">
@@ -274,7 +490,58 @@ const addTriggerConditions = () => {};
             <span> 添加触发条件 </span>
           </AFlex></AButton
         >
+        <br />
+
+        <ASelect class="status-style" v-model:value="alarmForm.conditionLogic">
+          <ASelectOption :value="0">且满足任一状态</ASelectOption>
+          <ASelectOption :value="1">且满足全部状态</ASelectOption>
+        </ASelect>
+        <br />
+        <div v-for="(item, index) in judgmentConditionList" :key="index">
+          <AlarmConditions
+            ref="judgmentConditionRefs"
+            @deleteClick="deleteJudgmentClick"
+            :index="index"
+            :form="item"
+            :alarm-condition="alarmCondition"
+            :execution-action="executionAction"
+            :alarm-repeat-time="alarmRepeatTime"
+            :alarm-alg-operator="alarmAlgOperator"
+            :alarm-device-state="alarmDeviceState"
+          />
+        </div>
+
+        <AButton type="primary" ghost class="icon-button button-top" @click="addJudgmentConditions">
+          <AFlex align="center">
+            <SvgIcon name="plus" />
+            <span> 添加判断条件 </span>
+          </AFlex></AButton
+        >
+        <br />
+
+        <div class="execute-text">就</div>
+        <div v-for="(item, index) in executionActionList" :key="index">
+          <AlarmExecution
+            ref="executionActionRefs"
+            @deleteClick="deleteExecutionClick"
+            :index="index"
+            :form="item"
+            :execution-action="executionAction"
+            :alarm-notify-method="alarmNotifyMethod"
+          />
+        </div>
+
+        <AButton type="primary" ghost class="icon-button button-top" @click="addExecutionConditions">
+          <AFlex align="center">
+            <SvgIcon name="plus" />
+            <span> 添加执行动作 </span>
+          </AFlex></AButton
+        >
       </AForm>
+      <AFlex align="center" class="enable-style">
+        <div class="enable-text">启用</div>
+        <ASwitch v-model:checked="alarmForm.enabled" />
+      </AFlex>
     </AModal>
     <AModal v-model:open="historyOpen" title="历史报警" width="920px" :footer="null">
       <ATable :columns="historyColumns" :data-source="historyData" :pagination="false">
@@ -310,6 +577,35 @@ const addTriggerConditions = () => {};
 </template>
 
 <style lang="scss" scoped>
+.enable-text {
+  margin-right: 32px;
+  font-size: 16px;
+  font-style: normal;
+  font-weight: 500;
+  line-height: 24px;
+  color: #333;
+  text-align: left;
+}
+
+.enable-style {
+  margin-top: 40px;
+}
+
+.execute-text {
+  margin-top: 40px;
+  font-size: 16px;
+  font-style: normal;
+  font-weight: 500;
+  line-height: 24px;
+  color: #333;
+  text-align: left;
+}
+
+.status-style {
+  width: 192px;
+  margin-top: 40px;
+}
+
 .query-bottom {
   margin-bottom: 8px;
 }