Sfoglia il codice sorgente

Merge branch 'algoConfg-fix' into 'dev'

算法配置bug修复

See merge request skyeye/skyeye_frontend/skyeye-admin!33
航飞 楼 1 anno fa
parent
commit
49cd651e3f

BIN
public/favicon.ico


+ 44 - 16
src/views/cameras/preview/components/AlgorithmsSetting/AddAlgoDialog.vue

@@ -3,23 +3,39 @@
     + 添加算法</ElButton
   >
 
-  <ElDialog v-model="visible" title="添加算法" @close="handleClose" width="500px">
-    <ElSelect v-model="selectedIds" multiple style="width: 100%" size="small">
-      <ElOption
-        v-for="item in allAlgoList"
-        :key="item.id"
-        :value="item.id"
-        :label="item.name"
-        :disabled="!!isAlgoBind(item.id)"
+  <ElDialog
+    v-model="visible"
+    title="添加算法"
+    @close="handleClose"
+    width="500px"
+    :close-on-click-modal="!algoListVisiable"
+  >
+    <div style="display: flex; justify-content: center; align-items: center">
+      <span>算法:</span>
+      <ElSelect
+        v-model="selectedIds"
+        multiple
+        style="width: 224px"
+        size="small"
+        @visible-change="handleVisibleChange"
+        placeholder="请为该相机选择关联的算法"
       >
-        {{ item.name }}
-        <span style="margin-left: 5px" v-if="isAlgoBind(item.id)">√</span>
-      </ElOption>
-    </ElSelect>
+        <ElOption
+          v-for="item in allAlgoList"
+          :key="item.id"
+          :value="item.id"
+          :label="item.name"
+          :disabled="!!isAlgoBind(item.id)"
+        >
+          {{ item.name }}
+          <span style="margin-left: 5px" v-if="isAlgoBind(item.id)">√</span>
+        </ElOption>
+      </ElSelect>
+    </div>
 
     <template #footer>
       <el-button @click="handleClose">取消</el-button>
-      <el-button type="primary" @click="handleSubmit"> 保存 </el-button>
+      <el-button type="primary" @click="handleSubmit"> 确定 </el-button>
     </template>
   </ElDialog>
 </template>
@@ -39,10 +55,18 @@
   const cameraDetailStore = useCameraDetailStore();
 
   const visible = ref(false);
+  const algoListVisiable = ref(false);
   const handleClose = () => {
     visible.value = false;
   };
 
+  const handleVisibleChange = (visible: boolean) => {
+    const t = setTimeout(() => {
+      algoListVisiable.value = visible;
+      clearTimeout(t);
+    }, 100);
+  };
+
   const handleSubmit = () => {
     if (selectedIds.value?.length < 1) {
       ElMessage.warning({ message: '请选择算法' });
@@ -53,10 +77,10 @@
       cameraId: cameraDetailStore.cameraId,
     }).then((res) => {
       console.log('createAlgo ok', res);
-      selectedAlgoId.value = selectedIds.value?.[0];
+      // selectedAlgoId.value = selectedIds.value?.[0];
       getCameraAlgoList(cameraDetailStore.cameraId);
+      ElMessage.success('添加成功,请完成算法参数配置后生效');
       visible.value = false;
-      ElMessage.success('添加算法成功');
     });
   };
 
@@ -65,4 +89,8 @@
     selectedIds.value = undefined;
   };
 </script>
-<style scoped></style>
+<style scoped>
+  :deep(.el-select .el-input__inner) {
+    min-height: 32px;
+  }
+</style>

+ 15 - 10
src/views/cameras/preview/components/AlgorithmsSetting/AlgoParamsCard.vue

@@ -31,12 +31,11 @@
           <ElInputNumber
             v-model="algoParams[item.prop]"
             controls-position="right"
-            :min="0"
             :step="1"
             style="width: 186px; margin-right: 5px"
             :disabled="!algoParams.label"
+            placeholder="请输入检测数量"
           />
-          <span>s</span>
         </el-form-item>
         <el-form-item
           v-if="item.type === 'confidence'"
@@ -116,6 +115,7 @@
   import { Delete, Edit } from '@element-plus/icons-vue';
   import { SaveOutline } from '@vicons/ionicons5';
   import { labelNameMap } from './types';
+  import { getCriticalCounts } from './utils';
 
   const props = defineProps<{
     id: string;
@@ -148,11 +148,17 @@
       .map((item) => item.id);
   };
 
-  const algoParams = computed(
-    () =>
-      selectedAlgoDetail.value.metaValues.find((item) => item.id === props.id) ||
-      ({} as AlgoParamMetaItem),
-  );
+  const algoParams = computed(() => {
+    const countList = getCriticalCounts(selectedAlgoDetail.value.extra);
+    const index = selectedAlgoDetail.value.metaValues.findIndex((item) => item.id === props.id);
+    if (index < 0) {
+      return {} as AlgoParamMetaItem;
+    } else {
+      const param = selectedAlgoDetail.value.metaValues[index];
+      param.criticalCount = countList && countList.length > index ? countList[index] : 0;
+      return param;
+    }
+  });
 
   const paramItems = ref([
     { label: '', type: 'label', prop: 'label' },
@@ -166,7 +172,6 @@
     (val) => {
       if (!val) return;
       const meta = metaObjList.value.find((item) => item.label === val);
-      algoParams.value.criticalCount = 0;
       algoParams.value.confidence = meta.confidence * 100;
       algoParams.value['min-width'] = meta['min-width'];
       algoParams.value['min-height'] = meta['min-height'];
@@ -209,7 +214,7 @@
         ];
       }
       if (param.type === 'criticalCount') {
-        rule[`${param.label}.criticalCount`] = [
+        rule[`${param.label}${param.label ? '.' : ''}criticalCount`] = [
           {
             required: true,
             message: '请输入检测数量',
@@ -219,7 +224,7 @@
         ];
       }
       if (param.type === 'confidence') {
-        rule[`${param.label}.confidence`] = [
+        rule[`${param.label}${param.label ? '.' : ''}confidence`] = [
           {
             required: true,
             message: '请输入检测置信度',

+ 14 - 0
src/views/cameras/preview/components/AlgorithmsSetting/AlgoPeriodCard.vue

@@ -151,6 +151,13 @@
           isConflict = true;
           break;
         }
+        if (
+          timeItem.value.startDay < dayRange.startDay! &&
+          timeItem.value.endDay > dayRange.endDay!
+        ) {
+          isConflict = true;
+          break;
+        }
       }
       if (isConflict) {
         ElMessage.error('日期存在冲突');
@@ -182,6 +189,13 @@
           isConflict = true;
           break;
         }
+        if (
+          hm2Minutes(curTimes!.startTime) < hm2Minutes(item.startTime) &&
+          hm2Minutes(curTimes!.endTime) > hm2Minutes(item.endTime)
+        ) {
+          isConflict = true;
+          break;
+        }
       }
       if (isConflict) {
         ElMessage.error('时间存在冲突');

+ 92 - 40
src/views/cameras/preview/components/AlgorithmsSetting/AlgoSettingCard.vue

@@ -1,31 +1,39 @@
 <template>
-  <div class="algoCardWrapper">
+  <div id="algoCardWrapper" class="algoCardWrapper">
     <div class="algoCardTitle">
       <div>{{ selectedAlgoDetail?.algoInfo?.name }}</div>
-      <ElSwitch
-        v-model="selectedAlgoDetail.enableCardBool"
-        size="small"
-        @change="handleAlgoEnable"
-      />
+      <div style="display: flex; align-items: center">
+        <ElSwitch
+          v-model="selectedAlgoDetail.enableCardBool"
+          size="small"
+          @change="handleAlgoEnable"
+        />
+        <el-tooltip class="box-item" effect="dark" placement="top">
+          <template #content> 关闭后该算法对<br />此台相机不生效</template>
+          <el-icon color="#d4d5d8" :size="16" class="tipIcon"><InfoCircleOutlined /></el-icon>
+        </el-tooltip>
+      </div>
     </div>
     <div class="algoCardMain">
-      <div class="algoRow">
+      <div class="algoRow" style="display: flex; align-items: center">
         <div class="algoLabel">绘制电子围栏:</div>
         <div>
           <div style="display: flex; align-items: center">
             <ElSwitch v-model="selectedAlgoDetail.electronicFenceBool" size="small" />
-            <el-tooltip
-              class="box-item"
-              effect="dark"
-              content="打开开关,绘制电子围栏。"
-              placement="top"
-            >
-              <el-icon color="#e2e2e2" class="tipIcon"><InfoCircleOutlined /></el-icon>
+            <el-tooltip class="box-item" effect="dark" placement="top">
+              <template #content>
+                默认检测全部范围,如需<br />指定范围,可打开开关,<br />在相机界面完成绘制
+              </template>
+              <el-icon color="#e2e2e2" :size="16" class="tipIcon"><InfoCircleOutlined /></el-icon>
             </el-tooltip>
             <!-- <span style="font-size: 10px; margin-left: 20px; color: #262626"
               >备注:请绘制电子围栏</span
             > -->
-            <el-radio-group v-model="selectedAlgoDetail.regionJudge" class="ml-4">
+            <el-radio-group
+              v-model="selectedAlgoDetail.regionJudge"
+              :disabled="!selectedAlgoDetail.electronicFenceBool"
+              class="ml-3"
+            >
               <el-radio :label="0">内</el-radio>
               <el-radio :label="1">外</el-radio>
             </el-radio-group>
@@ -52,7 +60,11 @@
       <div class="algoRow">
         <div class="algoLabel">检测元数据:</div>
         <div class="algoTimeContent">
-          <div class="timeAdd" @click="handleAddMetaObj">
+          <div
+            class="timeAdd"
+            :class="{ addDisable: unEmptyLabels.length >= selectedAlgoDetail.metaValues.length }"
+            @click="handleAddMetaObj"
+          >
             <el-icon color="#d0d0d0"><Plus /></el-icon>
           </div>
           <div class="timeList">
@@ -98,11 +110,13 @@
           :min="1"
           size="small"
           style="width: 186px"
+          placeholder="请输入允许的最长时间"
+          @blur="checkTimeWindowValid"
         />
         <span style="font-size: 12px; margin-left: 5px">S</span>
       </div>
       <div style="display: flex; justify-content: flex-end">
-        <ElButton size="small" @click="handleRemoveAlgo" :disabled="!selectedAlgoId">删除</ElButton>
+        <ElButton size="small" :disabled="!selectedAlgoId" @click="handleRemoveAlgo">取消</ElButton>
         <ElButton size="small" type="primary" @click="handleSave" :disabled="!selectedAlgoId"
           >保存</ElButton
         >
@@ -118,6 +132,7 @@
     ElInputNumber,
     ElTimePicker,
     ElMessage,
+    ElMessageBox,
   } from 'element-plus';
   import { CircleCloseFilled, Plus } from '@element-plus/icons-vue';
   import { storeToRefs } from 'pinia';
@@ -130,7 +145,7 @@
   import PeriodCard from './AlgoPeriodCard.vue';
   import ParamCard from './AlgoParamsCard.vue';
   import { uid } from 'uid';
-  import { ref } from 'vue';
+  import { computed, ref } from 'vue';
 
   // const { data: algoList, loading } = useAllAlgos();
   const cameraAlgoStore = useCameraAlgoStore();
@@ -157,7 +172,7 @@
 
   const emits = defineEmits<{
     (e: 'onSubmit', param: Param): Promise<unknown>;
-    (e: 'onRemove', algoId: number): Promise<unknown>;
+    (e: 'onCancel', algoId: number): Promise<unknown>;
   }>();
 
   const periodCardRefs = ref<any>([]);
@@ -175,19 +190,22 @@
     selectedAlgoDetail.value.timeRangeArr.push(createDefaultTime());
   };
 
+  const unEmptyLabels = computed(() => {
+    return selectedAlgoDetail.value.metaValues.filter((item) => item.label);
+  });
+
   const handleAddMetaObj = () => {
-    const unemptyList = selectedAlgoDetail.value.metaValues.filter((item) => item.label);
-    if (unemptyList.length < selectedAlgoDetail.value.metaValues.length) {
+    if (unEmptyLabels.value.length < selectedAlgoDetail.value.metaValues.length) {
       selectedAlgoDetail.value.metaValues
         .filter((item) => !item.label)
         .forEach((val) => markedParamCardIds.value.push(val.id));
       ElMessage.error('存在未完善的检测元数据');
       return;
     }
-    if (unemptyList.length == metaObjList.value.length) {
-      ElMessage.warning('暂无更多检测对象');
-      return;
-    }
+    // if (unemptyList.length == metaObjList.value.length) {
+    //   ElMessage.warning('暂无更多检测对象');
+    //   return;
+    // }
     selectedAlgoDetail.value.metaValues.push({ id: uid() } as AlgoParamMetaItem);
   };
 
@@ -213,6 +231,10 @@
     );
   };
 
+  const checkTimeWindowValid = () => {
+    selectedAlgoDetail.value.timeWindow = Math.ceil(selectedAlgoDetail.value.timeWindow || 1);
+  };
+
   const handleSave = async () => {
     //判断时间段是否合格
     if (markedTimeRangeIds.value.length > 0) {
@@ -249,23 +271,22 @@
     //判断元数据是否合格
     for (let i = 0; i < paramCardRefs.value.length; i++) {
       const item = paramCardRefs.value[i];
-      await item.checkValid().then((res) => {
-        if (res) {
-        } else {
-          ElMessage.error('请正确填写检测元数据');
-          return;
-        }
-      });
+      const res = await item.checkValid();
+      if (res) {
+      } else {
+        ElMessage.error('请正确填写检测元数据');
+        return;
+      }
     }
 
     //判断时间段和元数据都有值
     const detail = selectedAlgoDetail.value;
     if (!detail) return;
     const timeRanges = detail.timeRangeArr;
-    if (timeRanges.length == 0) {
-      ElMessage.error('至少添加一个检测时间段');
-      return;
-    }
+    // if (timeRanges.length == 0) {
+    //   ElMessage.error('至少添加一个检测时间段');
+    //   return;
+    // }
     const metaValues = detail.metaValues;
     if (metaValues.length == 0) {
       ElMessage.error('至少添加一个检测元数据');
@@ -320,7 +341,22 @@
 
   const handleRemoveAlgo = () => {
     if (!selectedAlgoId.value) return;
-    emits('onRemove', selectedAlgoId.value);
+    const el = document.getElementById('algoCardWrapper') as HTMLElement;
+    ElMessageBox.confirm(
+      '<strong>确认取消算法配置吗?</strong><br />取消后配置的参数将不会被保存。',
+      '',
+      {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning',
+        dangerouslyUseHTMLString: true,
+        appendTo: el,
+      },
+    )
+      .then(() => {
+        emits('onCancel', selectedAlgoId.value!);
+      })
+      .catch(() => {});
   };
 </script>
 <style scoped>
@@ -328,7 +364,7 @@
     border: 1px solid #ccc;
     border-radius: 4px;
     /* padding: 10px; */
-    width: 770px;
+    width: 780px;
   }
 
   .algoRow {
@@ -338,6 +374,8 @@
 
   .algoLabel {
     margin-right: 10px;
+    width: 90px;
+    text-align: right;
   }
   .algoTimeContent {
     display: flex;
@@ -365,7 +403,7 @@
   }
 
   .algoCardMain {
-    padding: 10px 20px;
+    padding: 10px 15px;
   }
 
   .timeAdd {
@@ -378,9 +416,15 @@
     justify-content: center;
     align-items: center;
     margin-right: 5px;
+    cursor: pointer;
   }
+  .addDisable {
+    opacity: 0.5;
+    cursor: not-allowed;
+  }
+
   .timeList {
-    width: 608px;
+    width: 610px;
     display: flex;
     flex-wrap: wrap;
     align-items: flex-start;
@@ -388,4 +432,12 @@
   .tipIcon {
     margin-left: 10px;
   }
+
+  :deep(.ml-4) {
+    margin-left: 0 !important;
+  }
+  :deep(.el-message-box__status.el-icon) {
+    top: 0 !important;
+    transform: none !important;
+  }
 </style>

+ 45 - 5
src/views/cameras/preview/components/AlgorithmsSetting/AlgoTag.vue

@@ -1,25 +1,65 @@
 <template>
-  <div class="tagWrapper">
+  <div
+    class="tagWrapper"
+    @mouseenter="isHover = true"
+    @mouseleave="isHover = false"
+    :style="{ opacity: isHover ? 0.7 : 1 }"
+  >
     <ElTag
       hit
       :type="props.isActive ? '' : 'info'"
       :class="{ isOpen: props.isOpen, isClose: !props.isOpen }"
-      >{{ props.label }}</ElTag
+      @click="handleHit"
     >
+      {{ props.label }}
+    </ElTag>
+    <el-icon v-show="isHover" color="#8f8f8f" style="margin: 4px" @click="handleRemoveAlgo">
+      <CircleCloseFilled />
+    </el-icon>
   </div>
 </template>
 <script lang="ts" setup>
-  const props = defineProps<{ isActive: boolean; label: string; isOpen: boolean }>();
+  import { CircleCloseFilled } from '@element-plus/icons-vue';
+  import { ref } from 'vue';
+
+  const props = defineProps<{
+    isActive: boolean;
+    label: string;
+    isOpen: boolean;
+    algoId: number;
+  }>();
+  const emits = defineEmits<{
+    (e: 'onRemove', algoId: number): Promise<unknown>;
+    (e: 'onHit', algoId: number): Promise<unknown>;
+  }>();
+
+  const isHover = ref(false);
+
+  const handleRemoveAlgo = () => {
+    emits('onRemove', props.algoId);
+  };
+
+  const handleHit = () => {
+    emits('onHit', props.algoId);
+  };
 </script>
 <style scoped>
   .tagWrapper {
     margin: 10px 0;
     cursor: pointer;
+    display: flex;
+    align-items: center;
+  }
+
+  .el-tag--info.isOpen {
+    background-color: #fafafa;
+    color: #409eff;
+    border-color: #909399;
   }
 
   .el-tag--info.isClose {
     background-color: #fafafa;
-    color: #bfbfbf;
-    border-color: #dadada;
+    color: #909399;
+    border-color: #909399;
   }
 </style>

+ 53 - 8
src/views/cameras/preview/components/AlgorithmsSetting/AlgorithmsSetting.vue

@@ -1,5 +1,5 @@
 <template>
-  <div>
+  <div id="algoSetting">
     <div style="font-size: 14px; font-weight: bold">算法配置</div>
     <div style="display: flex">
       <div class="algoTagWrapper">
@@ -9,15 +9,17 @@
           v-for="item in cameraAlgoList"
           :key="item.code"
           :label="item.algoInfo?.name"
+          :algo-id="item.algoInfo.id"
           :is-active="item.algoId === selectedAlgoId"
-          @click="handleSelectAlgo(item.algoId)"
+          @on-hit="handleSelectAlgo(item.algoId)"
+          @on-remove="handleRemove"
           :is-open="item.status === ALGO_ENABLED_STATUS.enabled"
         />
       </div>
       <div>
         <AlgoSettingCard
           @on-submit="handleSubmit"
-          @on-remove="handleRemove"
+          @on-cancel="handleCancel"
           v-if="selectedAlgoId"
         />
         <div style="color: #ccc; margin-top: 20px" v-else>请选择左侧算法</div>
@@ -34,7 +36,7 @@
     updateCameraAlgoApi,
     FENCE_ENBALED_STATUS,
   } from '@/api/camera/camera-preview';
-  import { ElMessage } from 'element-plus';
+  import { ElMessage, ElMessageBox } from 'element-plus';
   import AlgoTag from './AlgoTag.vue';
   import useFenceStore from '../../store/useFenceStore';
   import useCameraDetailStore from '../../store/useCameraDetailStore';
@@ -45,6 +47,7 @@
     getDetectionJSON,
     getDetectionTimeJSON,
     getInferCode,
+    getAlgoType,
     getMetaValues,
     getExtraCommonInfo,
     getDetectionTime,
@@ -61,7 +64,29 @@
   const cameraDetailStore = useCameraDetailStore();
 
   const handleSelectAlgo = (algoId: number) => {
-    if (algoId !== selectedAlgoId.value) {
+    if (selectedAlgoId.value === algoId) {
+      return;
+    }
+    if (selectedAlgoId.value) {
+      const el = document.getElementById('algoSetting') as HTMLElement;
+      ElMessageBox.confirm(
+        '<strong>确认切换算法配置吗?</strong><br />切换后未保存的算法配置将被丢弃。',
+        'warning',
+        {
+          confirmButtonText: '确定',
+          cancelButtonText: '取消',
+          type: 'warning',
+          dangerouslyUseHTMLString: true,
+          appendTo: el,
+        },
+      )
+        .then(() => {
+          if (algoId !== selectedAlgoId.value) {
+            selectedAlgoId.value = algoId;
+          }
+        })
+        .catch(() => {});
+    } else {
       selectedAlgoId.value = algoId;
     }
   };
@@ -101,7 +126,7 @@
     });
 
     //是否有窗口时间
-    if (commonInfo.timeWindow || commonInfo.timeWindow == 0) {
+    if (commonInfo.timeWindow) {
       selectedAlgoDetail.value.timeWindow = commonInfo.timeWindow;
     }
   });
@@ -114,10 +139,15 @@
     inferParams.regionJudge = param.regionJudge;
     inferParams.criticalCounts = param.criticalCounts;
     inferParams.judge = param.judge;
+    inferParams.algoCode = selectedAlgoDetail.value.algoInfo.code;
+    inferParams.algoType = getAlgoType(selectedAlgoDetail.value.algoInfo.extra);
+    if (param.timeWindow) {
+      inferParams.timeWindow = param.timeWindow;
+    }
     const extraValue = {
       inferCode: param.inferCode,
       inferParams: [inferParams],
-    };
+    } as any;
     const newParam = {
       cameraId: cameraId,
       electronicFence: param.electronicFence,
@@ -131,6 +161,7 @@
       updateCameraAlgoApi({ ...newParam, id: param.id }).then(() => {
         ElMessage.success('更新成功');
         getCameraAlgoList(cameraId);
+        selectedAlgoId.value = undefined;
       });
     }
   };
@@ -140,13 +171,27 @@
     deleteCameraAlgoApi({ algoId, cameraId: cameraDetailStore.cameraId }).then(() => {
       ElMessage.success('删除成功');
       getCameraAlgoList(cameraDetailStore.cameraId);
-      selectedAlgoId.value = undefined;
+      if (selectedAlgoId.value === algoId) {
+        selectedAlgoId.value = undefined;
+      }
     });
   };
+
+  const handleCancel = (algoId: number) => {
+    if (selectedAlgoId.value !== algoId) {
+      return;
+    }
+    selectedAlgoId.value = undefined;
+  };
 </script>
 <style scoped>
   .algoTagWrapper {
     min-width: 150px;
     margin-right: 15px;
   }
+
+  :deep(.el-message-box__status.el-icon) {
+    top: 0 !important;
+    transform: none !important;
+  }
 </style>

+ 17 - 1
src/views/cameras/preview/components/AlgorithmsSetting/utils.ts

@@ -104,6 +104,22 @@ export const getInferCode = (extra: string | undefined | null) => {
   return extraObj?.inferCode || '';
 };
 
+export const getAlgoType = (extra: string | undefined | null) => {
+  if (!extra) return 0;
+  const extraObj = JSON.parse(extra);
+  const infers = extraObj?.inferParams;
+  if (!infers || infers.length === 0) return 0;
+  return infers[0]?.algoType || 0;
+};
+
+export const getCriticalCounts = (extra: string | undefined | null) => {
+  if (!extra) return [];
+  const extraObj = JSON.parse(extra);
+  const infers = extraObj?.inferParams;
+  if (!infers || infers.length === 0) return [];
+  return infers[0]?.criticalCounts || [];
+};
+
 export const getExtraCommonInfo = (detail: CameraAlgoItem | undefined | null) => {
   if (!detail) return {};
   let extraValue = getCommonInfo(detail.extra);
@@ -134,7 +150,7 @@ const getCommonInfo = (extra: string | undefined | null): CommonInfo => {
   if (judge || judge == 0) {
     ret.judge = judge;
   }
-  if (timeWindow || timeWindow == 0) {
+  if (timeWindow) {
     ret.timeWindow = timeWindow;
   }
   return ret;