Ver código fonte

完成算法配置卡片的样式

louhangfei 2 anos atrás
pai
commit
d02dcda9b9

+ 1 - 0
package.json

@@ -57,6 +57,7 @@
     "print-js": "1.6.0",
     "qrcode": "1.5.1",
     "qs": "6.11.0",
+    "uid": "2.0.2",
     "url-join": "5.0.0",
     "vue": "3.3.4",
     "vue-hooks-plus": "1.8.6",

+ 17 - 10
pnpm-lock.yaml

@@ -83,6 +83,9 @@ dependencies:
   qs:
     specifier: 6.11.0
     version: 6.11.0
+  uid:
+    specifier: 2.0.2
+    version: 2.0.2
   url-join:
     specifier: 5.0.0
     version: 5.0.0
@@ -1105,6 +1108,11 @@ packages:
       '@jridgewell/sourcemap-codec': 1.4.15
     dev: true
 
+  /@lukeed/csprng@1.1.0:
+    resolution: {integrity: sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==}
+    engines: {node: '>=8'}
+    dev: false
+
   /@mapbox/node-pre-gyp@1.0.11:
     resolution: {integrity: sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==}
     hasBin: true
@@ -2487,7 +2495,7 @@ packages:
       normalize-path: 3.0.0
       readdirp: 3.6.0
     optionalDependencies:
-      fsevents: registry.npmmirror.com/fsevents@2.3.2
+      fsevents: 2.3.3
     dev: true
 
   /chownr@2.0.0:
@@ -4060,14 +4068,6 @@ packages:
   /fs.realpath@1.0.0:
     resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
 
-  /fsevents@2.3.2:
-    resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
-    engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
-    os: [darwin]
-    requiresBuild: true
-    dev: true
-    optional: true
-
   /fsevents@2.3.3:
     resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
     engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
@@ -6780,7 +6780,7 @@ packages:
       '@esbuild-kit/core-utils': 2.1.0
       '@esbuild-kit/esm-loader': 2.4.1
     optionalDependencies:
-      fsevents: registry.npmmirror.com/fsevents@2.3.2
+      fsevents: 2.3.3
     dev: true
 
   /type-check@0.4.0:
@@ -6828,6 +6828,13 @@ packages:
     engines: {node: '>=4.2.0'}
     hasBin: true
 
+  /uid@2.0.2:
+    resolution: {integrity: sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g==}
+    engines: {node: '>=8'}
+    dependencies:
+      '@lukeed/csprng': 1.1.0
+    dev: false
+
   /universalify@0.1.2:
     resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==}
     engines: {node: '>= 4.0.0'}

+ 47 - 8
src/api/camera/camera-preview.ts

@@ -2,6 +2,7 @@
 // cameraPreview/getList
 
 import { http } from '@/utils/http/axios';
+import qs from 'qs';
 
 /** 相机树的结点类型 */
 export enum CameraTreeNodeType {
@@ -49,21 +50,59 @@ interface AlgoItem {
   createdAt: string;
   updatedAt: string;
 }
-
-/** 查询某个camera下的算法 */
-export const getCameraAlgo = (cameraId: number) => {
+/** 查询所有的算法 */
+export const getAllAlgosApi = (params: { pageNumber: number; pageSize: number }) => {
   return http.request<AlgoItem[]>({
+    url: '/cameraPreview/getAlgoList',
+    method: 'get',
+    params,
+  });
+};
+
+/** 相机关联的算法信息 */
+interface CameraAlgoItem {
+  algoId: number;
+  cameraId: number;
+  code: string;
+  name: string;
+  detectionFrequency: number;
+  detectionTime: string;
+  electronicFence: string;
+  status: AlgoStatus;
+  algoInfo: AlgoItem;
+}
+
+/** 查询某个camera下的所有算法算法 */
+export const getCameraAlgoListApi = (cameraId: number) => {
+  return http.request<CameraAlgoItem[]>({
     url: '/cameraPreview/getAlgo',
     method: 'get',
     params: { cameraId },
   });
 };
 
-/** 查询所有的算法 */
-export const getAllAlgos = (params: { pageNumber: number; pageSize: number }) => {
+interface SaveCameraAlgoParam {
+  algoId: number;
+  cameraId: number;
+  detectionFrequency: number;
+  detectionTime: string;
+  electronicFence: string;
+  status: 0 | 1;
+}
+/** 保存相机的某个算法 */
+export const saveCameraAlgoApi = (param: SaveCameraAlgoParam) => {
   return http.request({
-    url: '/api/cameraAlgo/getList',
-    method: 'get',
-    params,
+    url: '/cameraPreview/saveAlgo',
+    data: param,
+    method: 'post',
+  });
+};
+
+/** 删除相机的某个算法 */
+export const deleteCameraAlgoApi = (params: { cameraId: number; algoId: number }) => {
+  const paramString = qs.stringify(params);
+  return http.request({
+    url: '/cameraPreview/deleteAlgo?' + paramString,
+    method: 'delete',
   });
 };

+ 211 - 0
src/views/cameras/preview/components/AlgorithmsSetting/AlgoSettingCard.vue

@@ -0,0 +1,211 @@
+<template>
+  <div class="algoCardWrapper">
+    <div style="display: flex; justify-content: space-between">
+      <ElSelect v-model="currentAlgoId" style="width: 150px">
+        <ElOption v-for="item in data" :key="item.id" :value="item.id" :label="item.name">
+          {{ item.name }}
+          <span style="margin-left: 5px" v-if="isAlgoAdded(item.id)">√</span>
+        </ElOption>
+      </ElSelect>
+      <ElSwitch v-model="enableCard" />
+    </div>
+    <div>
+      <div class="algoRow">
+        <div class="algoLabel">电子围栏:</div>
+        <div>
+          <div>
+            <ElSwitch v-model="electronicFence" size="small" />
+            <span style="font-size: 10px; margin-left: 20px; color: #262626"
+              >备注:请绘制电子围栏</span
+            >
+          </div>
+          <div class="presetList"> 预置位1 预置位2 预置位3 </div>
+        </div>
+      </div>
+      <div class="algoRow">
+        <div class="algoLabel">检测时间:</div>
+        <div>
+          <div v-for="x in timeRangeArr" :key="x.id">
+            <el-time-picker
+              v-model="x.value"
+              is-range
+              arrow-control
+              range-separator="-"
+              start-placeholder="Start time"
+              end-placeholder="End time"
+              :clearable="false"
+              size="small"
+              style="width: 180px; margin-bottom: 10px"
+              @change="handleTimeChange(x.id, $event)"
+            />
+            <span @click="removeTime(x.id)" v-if="timeRangeArr.length > 1">
+              <el-icon style="color: #8c8c8c"><CircleCloseFilled /></el-icon
+            ></span>
+          </div>
+          <div @click="handleAddTimeRange"
+            ><el-icon style="cursor: pointer; background-color: #f5f5f5; font-size: 20px"
+              ><CirclePlus /></el-icon
+          ></div>
+        </div>
+      </div>
+      <div class="algoRow" style="align-items: center">
+        <div class="algoLabel">检测频率:</div>
+        <ElInputNumber
+          v-model="detectionNum"
+          controls-position="right"
+          :min="0"
+          size="small"
+          style="width: 80px"
+        />
+        <ElSelect size="small" style="width: 60px; margin-left: 10px" v-model="detectionUnit">
+          <ElOption
+            v-for="x in frequencyOptions"
+            :key="x.value"
+            :value="x.value"
+            :label="x.label"
+          />
+        </ElSelect>
+        <span style="font-size: 12px; margin-left: 5px">/次</span>
+      </div>
+      <div style="display: flex; justify-content: flex-end">
+        <ElButton size="small" @click="emits('onRemove', currentAlgoId)">删除</ElButton>
+        <ElButton size="small" type="primary" @click="handleSave">保存</ElButton>
+      </div>
+    </div>
+  </div>
+</template>
+<script lang="ts" setup>
+  import { ElSelect, ElOption, ElSwitch, ElInputNumber, ElTimePicker } from 'element-plus';
+  import { CirclePlus, CircleCloseFilled } from '@element-plus/icons-vue';
+  import useAllAlgos from './useAllAlgos';
+  import { ref, watch } from 'vue';
+  import { uid } from 'uid';
+  import { storeToRefs } from 'pinia';
+  import useCameraAlgoStore from '../../store/useCameraAlgoStore';
+  import { STATUS } from './types';
+
+  const { data, loading } = useAllAlgos();
+
+  const cameraAlgoStore = useCameraAlgoStore();
+
+  const { data: cameraAlogList } = storeToRefs(cameraAlgoStore);
+
+  const props = defineProps<{ activeAlgoId: number | null }>();
+
+  interface Param {
+    /** 算法id */
+    algoId: number;
+    /** 检测频率,单位是秒 */
+    detectionFrequency: number;
+    /** 检测时间段,可以有多个,转化为字符串,格式为 09:00-10:00;11:00-12:00 */
+    detectionTime: string;
+    /** 电子围栏是否开启 */
+    electronicFence: STATUS;
+    /** 算法是否启用 */
+    status: STATUS;
+  }
+
+  const emits = defineEmits<{
+    (e: 'onSubmit', param: Param): Promise<unknown>;
+    (e: 'onRemove', algoId: number): Promise<unknown>;
+  }>();
+
+  enum FrequencyEnum {
+    second = 1,
+    miniute = 60,
+    hour = 3600,
+  }
+
+  watch(
+    () => props.activeAlgoId,
+    () => {
+      if (props.activeAlgoId) {
+        currentAlgoId.value = props.activeAlgoId;
+      }
+    },
+    {
+      immediate: true,
+    },
+  );
+
+  const frequencyOptions = [
+    { label: '秒', value: FrequencyEnum.second },
+    { label: '分钟', value: FrequencyEnum.miniute },
+    { label: '小时', value: FrequencyEnum.hour },
+  ];
+  const createDefaultTime = () => {
+    return { id: uid(), value: [new Date(), new Date()] as [Date, Date] };
+  };
+
+  const isAlgoAdded = (algoId: number) => {
+    return cameraAlogList.value?.some((x) => x.id === algoId);
+  };
+
+  const timeRangeArr = ref([createDefaultTime()]);
+
+  const detectionUnit = ref(FrequencyEnum.miniute);
+
+  const currentAlgoId = ref<number>(1);
+
+  /** 电子围栏开关 */
+  const electronicFence = ref(true);
+
+  const detectionNum = ref(5);
+
+  const enableCard = ref(true);
+
+  const handleAddTimeRange = () => {
+    timeRangeArr.value.push(createDefaultTime());
+  };
+
+  const handleTimeChange = (...rest) => {
+    console.log(timeRangeArr);
+  };
+
+  const removeTime = (id: string) => {
+    timeRangeArr.value = timeRangeArr.value.filter((x) => x.id !== id);
+  };
+
+  const getTimeStr = (d: Date) => {
+    return d.getHours() + ':' + d.getMinutes() + ':' + d.getSeconds();
+  };
+  const getTimeStrs = (d: [Date, Date]) => {
+    return getTimeStr(d[0]) + '-' + getTimeStr(d[1]);
+  };
+
+  const handleSave = () => {
+    const param = {
+      algoId: currentAlgoId.value,
+      detectionFrequency: detectionNum.value * detectionUnit.value,
+      detectionTime: timeRangeArr.value
+        .map((x) => {
+          return getTimeStrs(x.value);
+        })
+        .join(';'),
+      electronicFence: electronicFence.value ? STATUS.enabled : STATUS.disabled,
+      status: enableCard.value ? STATUS.enabled : STATUS.disabled,
+    };
+    emits('onSubmit', param);
+    console.log('param', param);
+  };
+</script>
+<style scoped>
+  .algoCardWrapper {
+    border: 1px solid #ccc;
+    border-radius: 4px;
+    padding: 10px;
+    width: 400px;
+  }
+
+  .algoRow {
+    display: flex;
+    margin: 10px;
+  }
+
+  .algoLabel {
+    margin-right: 10px;
+  }
+  .presetList {
+    font-size: 12px;
+  }
+</style>

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

@@ -0,0 +1,14 @@
+<template>
+  <div class="tagWrapper">
+    <ElTag hit :type="props.isActive ? '' : 'info'">{{ props.label }}</ElTag>
+  </div>
+</template>
+<script lang="ts" setup>
+  const props = defineProps<{ isActive: boolean; label: string }>();
+</script>
+<style scoped>
+  .tagWrapper {
+    margin: 10px;
+    cursor: pointer;
+  }
+</style>

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

@@ -3,25 +3,72 @@
     <div>算法配置</div>
     <div style="display: flex">
       <div style="width: 200px">
-        <div v-for="item in data" :key="item.code">{{ item.name }}</div>
+        <AlgoTag
+          v-for="item in data"
+          :key="item.code"
+          :label="item.algoInfo?.name"
+          :is-active="item.algoId === activeCameraAlgoId"
+          @click="handleSelectAlgo(item.algoId)"
+        />
+      </div>
+      <div>
+        <AlgoSettingCard
+          @on-submit="handleSubmit"
+          @on-remove="handleRemove"
+          :activeAlgoId="activeCameraAlgoId"
+        />
       </div>
-      <div style="flex-grow: 1">算法配置卡片 需要独立成一个组件</div>
     </div>
   </div>
 </template>
 <script lang="ts" setup>
-  import { onMounted } from 'vue';
+  import { onMounted, ref } from 'vue';
   import useCameraAlgoStore from '../../store/useCameraAlgoStore';
+  import AlgoSettingCard from './AlgoSettingCard.vue';
   import { storeToRefs } from 'pinia';
+  import { deleteCameraAlgoApi, saveCameraAlgoApi } from '@/api/camera/camera-preview';
+  import { useRouteQuery } from '@vueuse/router';
+  import { ElMessage, ElTag } from 'element-plus';
+  import AlgoTag from './AlgoTag.vue';
 
   const bindAlgoList = defineProps<{ bindAlgoList: any }>();
   const cameraAlgoStore = useCameraAlgoStore();
-  const { runAsync } = cameraAlgoStore;
 
+  const { getCameraAlgo } = cameraAlgoStore;
   const { data, loading } = storeToRefs(cameraAlgoStore);
 
+  const cameraId = useRouteQuery('cameraId', '', { transform: (str) => Number(str) });
+  const activeCameraAlgoId = ref<number | null>(null);
   onMounted(() => {
-    runAsync(2);
+    getCameraAlgo(cameraId.value);
   });
+
+  const handleSelectAlgo = (id: number) => {
+    activeCameraAlgoId.value = id;
+  };
+
+  const handleSubmit = (param) => {
+    console.log('submitParam', param);
+    const newParam = {
+      cameraId: cameraId.value,
+      electronicFence: '',
+      algoId: param.algoId,
+      detectionFrequency: param.detectionFrequency,
+      detectionTime: param.detectionTime,
+      status: param.status,
+    };
+    saveCameraAlgoApi(newParam).then(() => {
+      ElMessage.success('保存成功');
+      getCameraAlgo(cameraId.value);
+    });
+  };
+
+  const handleRemove = (algoId: number) => {
+    console.log('remove', algoId);
+    deleteCameraAlgoApi({ algoId, cameraId: cameraId.value }).then(() => {
+      ElMessage.success('删除成功');
+      getCameraAlgo(cameraId.value);
+    });
+  };
 </script>
 <style scoped></style>

+ 4 - 0
src/views/cameras/preview/components/AlgorithmsSetting/types.ts

@@ -0,0 +1,4 @@
+export enum STATUS {
+  enabled = 1;
+  disabled = 0;
+}

+ 11 - 0
src/views/cameras/preview/components/AlgorithmsSetting/useAllAlgos.ts

@@ -0,0 +1,11 @@
+import { getAllAlgosApi } from '@/api/camera/camera-preview';
+import { useRequest } from 'vue-hooks-plus';
+
+/** 获取所有的算法列表 */
+const useAllAlgos = () => {
+  const { data, loading } = useRequest(getAllAlgosApi);
+
+  return { data, loading };
+};
+
+export default useAllAlgos;

+ 11 - 3
src/views/cameras/preview/components/CameraTree/CameraTree.vue

@@ -11,12 +11,16 @@
 </template>
 <script lang="ts" setup>
   import { ElTree } from 'element-plus';
-  import { ref } from 'vue';
-  import useCameraDetail from '../../store/useCameraDetail';
+  import { Ref, ref } from 'vue';
+  import { useRouteQuery } from '@vueuse/router';
+  import useCameraDetail from '../../store/useCameraDetailStore';
   import { CameraTree, CameraTreeNodeType } from '@/api/camera/camera-preview';
+  import { onMounted } from 'vue';
   const props = defineProps<{ data }>();
 
   const { setDetail } = useCameraDetail();
+
+  const cameraId = useRouteQuery('cameraId');
   const defaultProps = {
     children: 'children',
     label: 'name',
@@ -26,10 +30,14 @@
     console.log('e', e);
     if (e.nodeType === CameraTreeNodeType.camera) {
       setDetail(e);
+      cameraId.value = String(e.id);
     }
   };
 
+  onMounted(() => {
+    console.log('cameraId', cameraId.value);
+  });
+
   const treeRef = ref(null);
 </script>
 <style scoped></style>
-../../../cameras/preview/store/useCameraDetail

+ 12 - 5
src/views/cameras/preview/store/useCameraAlgoStore.ts

@@ -1,13 +1,20 @@
-import { getCameraAlgo } from '@/api/camera/camera-preview';
+import { getCameraAlgoListApi } from '@/api/camera/camera-preview';
 import { defineStore } from 'pinia';
 import { useRequest } from 'vue-hooks-plus';
 
 const useCameraAlgoStore = defineStore('cameraAlgo', () => {
-  const { data, loading, runAsync } = useRequest((cameraId: number) => {
-    return getCameraAlgo(cameraId);
-  });
+  const { data, loading, runAsync } = useRequest(
+    (cameraId: number) => {
+      return getCameraAlgoListApi(cameraId);
+    },
+    { manual: true },
+  );
 
-  return { data, loading, runAsync };
+  const getAlgoDetail = (algoId: number) => {
+    return data.value?.find((x) => x.algoId === algoId);
+  };
+
+  return { data, loading, getCameraAlgo: runAsync, getAlgoDetail };
 });
 
 export default useCameraAlgoStore;

src/views/cameras/preview/store/useCameraDetail.ts → src/views/cameras/preview/store/useCameraDetailStore.ts