Selaa lähdekoodia

feat: 完成电子围栏的查询和新建

louhangfei 1 vuosi sitten
vanhempi
commit
c7498a0948

+ 59 - 13
src/api/camera/camera-preview.ts

@@ -207,41 +207,86 @@ export interface GetFenceParams {
 }
 
 /** 查询电子围栏 */
-export const getFenceApi = (params: GetFenceParams): Promise<{ id: number; electronicFencePolygon: string }> => {
+export const getFenceApi = (params: GetFenceParams): Promise<{ id: number; electronicFence: string }> => {
   return http.request({
-    url: '/admin/cameraPreview/queryFence',
-    method: 'GET',
+    url: '/admin/algo/queryFence',
+    method: 'POST',
     params,
   });
 };
 
+// export interface SaveFenceParams {
+//   algoId: number;
+//   cameraId: number;
+//   electronicFencePolygon: string;
+//   presetToken: string;
+// }
+/** 添加电子围栏 */
+// export const saveFenceApi = (data: SaveFenceParams) => {
+//   return http.request({
+//     url: '/admin/cameraPreview/saveFence',
+//     method: 'post',
+//     data,
+//   });
+// };
+
 export interface SaveFenceParams {
-  algoId: number;
+  /*相机id */
   cameraId: number;
-  electronicFencePolygon: string;
+
+  /*算法id */
+  algoId: number;
+
+  /*摄像头预置位token */
   presetToken: string;
+
+  /*电子围栏标签 */
+  fenceLabel?: string;
+
+  /*电子围栏名称 */
+  fenceName?: string;
+
+  /*电子围栏点位信息 */
+  fencePolygon: string;
 }
-/** 添加电子围栏 */
 export const saveFenceApi = (data: SaveFenceParams) => {
   return http.request({
-    url: '/admin/cameraPreview/saveFence',
+    url: '/admin/algo/saveFence',
     method: 'post',
     data,
   });
 };
 
 interface UpdateFenceParams {
-  algoId: number;
+  /*相机id */
   cameraId: number;
-  electronicFencePolygon: string;
-  id: number;
+
+  /*算法id */
+  algoId: number;
+
+  /*摄像头预置位token */
   presetToken: string;
+
+  /*电子围栏id */
+  fenceId: number;
+
+  /*电子围栏标签 */
+  fenceLabel?: string;
+
+  /*电子围栏名称 */
+  fenceName?: string;
+
+  /*电子围栏点位信息 */
+  fencePolygon: string;
+
+  /*是否开启(该算法电子围栏总开关) */
+  isDisabled?: boolean;
 }
 /** 编辑电子围栏 */
 export const editFenceApi = (data: UpdateFenceParams) => {
   return http.request({
-    url: '/admin/cameraPreview/updateFence',
-    method: 'put',
+    url: '/admin/algo/updateFence',
+    method: 'post',
     data,
   });
 };
@@ -311,7 +356,8 @@ interface PresetDetailItem {
 export const getPresetListApi = (cameraId: number) => {
   return http.request<PresetDetailItem[]>(
     {
-      url: `/camera/getPresets`,
+      url: `/onvif/getPresets`,
+      // url: `/admin/algo/queryCameraPreset`,
       method: 'get',
       params: { cameraId },
     },

+ 1 - 0
src/views/cameras/algo-params-setting/AlgoParamsSetting.vue

@@ -133,5 +133,6 @@
 
   .cameraView {
     width: 1230px;
+    position: relative;
   }
 </style>

+ 81 - 25
src/views/cameras/algo-params-setting/components/CameraViewSetting/CameraViewSetting.vue

@@ -17,7 +17,8 @@
             ref="fenceEditorRef"
             :dom-width="domWidth"
             :canvas-size="{ width: canvasWidth, height: canvasHeight }"
-            :line-points="fenceStore.serverFencePoints || []"
+            :line-points="fenceStore.serverFencePoints"
+            @save="handleSaveFence"
           />
         </div>
 
@@ -40,9 +41,8 @@
     </div>
   </div>
   <div class="presetWrapper">
-    <PresetSelect />
     <FenceToolbar
-      :style="{ display: drawable ? 'flex' : 'none' }"
+      :style="{ display: drawable ? 'block' : 'none' }"
       @remove="handleRemove"
       @save="handleSave"
       @toggle-editable="toggleEditable"
@@ -80,10 +80,11 @@
   import CameraViewScale from './CameraViewScale.vue';
   import { canvasHeight, canvasWidth, domHeight, domWidth } from './constants';
   import useFullscreen from 'vue-hooks-plus/lib/useFullscreen';
-  import { createCameraAlgoApi, updateCameraAlgoApi } from '@/api/camera/camera-preview';
+  import { createCameraAlgoApi, editFenceApi, saveFenceApi, updateCameraAlgoApi } from '@/api/camera/camera-preview';
   import { RegionJudge } from '../FenceToolbar/constants';
   import AlgoCanSelect from '../AlgoCanSelect/AlgoCanSelect.vue';
   import { AlgoDetail, queryAlgoInfoAllByCameraId } from '@/api/algo/algo';
+  import { FencePolygonPoints } from '../FenceEditorV2/types';
 
   const emits = defineEmits<{
     (e: 'changeTreeRender', render: number | string): unknown;
@@ -141,6 +142,69 @@
     }
   };
 
+  const newFencePoints = ref([
+    {
+      id: 1,
+      label: 'fence2',
+      name: '电子围栏2',
+      polygon: [
+        [906, 568],
+        [648, 706],
+        [848, 830],
+        [1002, 688],
+      ],
+    },
+    {
+      id: 2,
+      label: 'fence3',
+      name: '电子围栏3',
+      polygon: [
+        [806, 568],
+        [548, 206],
+        [748, 830],
+        [1002, 288],
+      ],
+    },
+  ]);
+
+  const valiateFenceDependence = () => {
+    const cameraId = cameraDetailStore.cameraId;
+    if (!cameraId) {
+      ElMessage.error('未选中相机');
+      return null;
+    }
+    const algoId = cameraAlgoStore.selectedAlgoId;
+    if (!algoId) {
+      ElMessage.error('未选中算法');
+      return null;
+    }
+    const presetToken = presetStore.currentPresetToken;
+    if (!presetToken) {
+      ElMessage.error('未选中预置位');
+      return null;
+    }
+    return { cameraId: cameraId, algoId: algoId, presetToken };
+  };
+
+  const handleSaveFence = (data: { fenceId: number; polygon: FencePolygonPoints }) => {
+    console.log('提交的fenceId', data);
+    const { fenceId, polygon } = data;
+    const validateResult = valiateFenceDependence();
+    if (!validateResult) return;
+    const newParam = {
+      ...validateResult,
+      fencePolygon: JSON.stringify(polygon),
+      fenceName: '名字测试1',
+    };
+    if (!fenceId) {
+      // 不存在的话,就新建电子围栏
+      saveFenceApi(newParam);
+    } else {
+      // 否则修改电子围栏
+      editFenceApi({ ...newParam, fenceId });
+    }
+  };
+
   const toggleRange = () => {
     const selectedAlgoDetail = cameraAlgoStore.selectedAlgoDetail;
     const cameraId = cameraDetailStore.cameraId;
@@ -167,28 +231,13 @@
   const handleSave = () => {
     const json = fenceEditorRef.value?.toObject();
     console.log('save json', json);
-    const cameraId = cameraDetailStore.cameraId;
-    if (!cameraId) {
-      ElMessage.error('未选中相机');
-      return;
-    }
-    const algoId = cameraAlgoStore.selectedAlgoId;
-    if (!algoId) {
-      ElMessage.error('未选中算法');
-      return;
-    }
-    const presetToken = presetStore.currentPresetToken;
-    if (!presetToken) {
-      ElMessage.error('未选中预置位');
-      return;
-    }
+    const validateResult = valiateFenceDependence();
+    if (!validateResult) return;
 
     fenceStore
       .saveFence({
-        cameraId: cameraId,
-        algoId: algoId,
-        presetToken,
-        electronicFencePolygon: JSON.stringify(json),
+        ...validateResult,
+        fencePolygon: JSON.stringify(json),
       })
       ?.then(() => {
         ElMessage.success('更新成功');
@@ -306,14 +355,21 @@
   }
 
   .presetWrapper {
+    position: absolute;
+    right: 0;
+    top: 0;
+    width: 260px;
+    height: 540px;
+    border: 1px solid #f00;
+    background: #fff;
     /* width: 962px; */
-    padding: 20px;
+    /* padding: 20px;
     padding-left: 15px;
     border-bottom: 1px solid #ccc;
     padding-bottom: 10px;
     margin-bottom: 15px;
     display: flex;
-    justify-content: space-between;
+    justify-content: space-between; */
   }
   .videoAlgoListWrapper {
     display: flex;

+ 1 - 1
src/views/cameras/algo-params-setting/components/FenceEditor/constants.ts

@@ -39,4 +39,4 @@ export type ServerLinePoint = [number, number];
 export type ServerLine = ServerLinePoint[];
 
 /** 图上所有的多边形 */
-export type ServerLines = ServerLine[];
+export type ServerLines = { id: number; name: string; label: string; polygon: ServerLine }[];

+ 23 - 32
src/views/cameras/algo-params-setting/components/FenceEditorV2/FenceEditor.vue

@@ -31,20 +31,26 @@
   import Konva from 'konva';
   import { computed, onMounted, onUnmounted, ref, watch } from 'vue';
   import FenceItem from './FenceItem.vue';
-  import { createCircleConfigItem, createGroupConfig } from './utils';
-  import { FenceGroup } from './types';
+  import { createCircleConfigItem, createGroupConfig, polygonPoint1ToPoint2 } from './utils';
+  import { FenceGroup, FencePolygonPoints, SingleFence } from './types';
   import { ElMessage } from 'element-plus';
   import { GROUP_NAME } from './constants';
 
   const props = defineProps<{
     /** 电子围栏的坐标 */
-    linePoints: [number, number][][];
+    linePoints: SingleFence[];
     /** 画布的大小 */
     canvasSize: { width: number; height: number };
     /** dom的真实尺寸 */
     domWidth: number;
   }>();
 
+  // 保存修改后的电子围栏, fenceId为空表示新建的,否则表示编辑已存在的
+  interface Save {
+    (e: 'save', data: { fenceId?: number; polygon: FencePolygonPoints }): unknown;
+  }
+  const emits = defineEmits<Save>();
+
   const scale = computed(() => {
     return props.domWidth / props.canvasSize.width;
   });
@@ -64,10 +70,11 @@
     (newLinePoints) => {
       const configs: FenceGroup[] =
         newLinePoints.map((points) => {
-          const flattenedPoints = points.reduce((total, next) => {
+          const polygonJSON = points.polygon || [];
+          const flattenedPoints = polygonJSON.reduce((total, next) => {
             return [...total, ...next];
           }, [] as number[]);
-          return createGroupConfig(flattenedPoints, scale.value);
+          return createGroupConfig({ fenceId: points.id, points: flattenedPoints, scale: scale.value });
         }) || [];
       fenceGroups.value = configs;
     },
@@ -120,7 +127,7 @@
 
     /** 如果还没开始画线,那么增加第一个点 */
     if (!drawingGroupId.value) {
-      const groupConfig = createGroupConfig(point, scale.value);
+      const groupConfig = createGroupConfig({ points: point, scale: scale.value });
       drawingGroupId.value = groupConfig.uid;
       currentGroupId.value = groupConfig.uid;
       groupConfig._temp.points = point;
@@ -147,7 +154,13 @@
           groupConfig.circleConfigs = [];
           currentGroupId.value = '';
         } else {
-          groupConfig.lineConfig.points = groupConfig._temp.points;
+          const points = groupConfig._temp.points;
+          groupConfig.lineConfig.points = points;
+          console.log('完成了一个电子围栏的编辑groupConfig', groupConfig);
+          const points2 = polygonPoint1ToPoint2(points);
+          if (points2) {
+            emits('save', { fenceId: groupConfig.fenceId, polygon: points2 });
+          }
         }
         drawingGroupId.value = '';
         groupConfig._temp.points = [];
@@ -165,11 +178,7 @@
       groupConfig._temp.points = finalPoints;
       currentGroupId.value = groupConfig.uid;
 
-      const circleConfig = createCircleConfigItem(
-        point,
-        groupConfig.circleConfigs.length,
-        scale.value,
-      );
+      const circleConfig = createCircleConfigItem(point, groupConfig.circleConfigs.length, scale.value);
       groupConfig.circleConfigs.push(circleConfig);
     }
   };
@@ -182,9 +191,7 @@
     if (!mousePosition?.x || !mousePosition?.y) return;
     const newPoint = [mousePosition.x, mousePosition.y];
     if (drawingGroupId.value) {
-      const groupConfig: FenceGroup | undefined = fenceGroups.value.find(
-        (x) => x.uid === drawingGroupId.value,
-      );
+      const groupConfig: FenceGroup | undefined = fenceGroups.value.find((x) => x.uid === drawingGroupId.value);
       if (!groupConfig) {
         console.error('drawingGroupId无效', drawingGroupId.value);
         return;
@@ -221,23 +228,7 @@
     const fenceGroups = stage?.find('.' + GROUP_NAME);
     const gropuPoints = fenceGroups
       ?.map((item) => {
-        const groupX = item.x();
-        const groupY = item.y();
-
-        const line = (item as Konva.Group).findOne(
-          (x: any) => x.className === 'Line',
-        ) as Konva.Line;
-        const points = line?.points();
-        /** 有些line对象存在,但是没有点坐标,所以要判断过滤一下 */
-        if (points && points.length > 0) {
-          const newPoints: number[][] = [];
-          /** 存到后端的时候,只给点的坐标信息,不会给group的位置信息,所以要将点的坐标加上group的位移,才是之后点的最终坐标 */
-          for (let i = 0; i < points.length; i += 2) {
-            newPoints.push([Math.floor(points[i] + groupX), Math.floor(points[i + 1] + groupY)]);
-          }
-          return newPoints;
-        }
-        return null;
+        return polygonPoint1ToPoint2(item);
       })
       .filter(Boolean);
     return gropuPoints;

+ 13 - 0
src/views/cameras/algo-params-setting/components/FenceEditorV2/types.ts

@@ -14,4 +14,17 @@ export interface FenceGroup {
   circleConfigs: FenceCircleConfig[];
   uid: string;
   name: string;
+  /** 电子围栏的id */
+  fenceId?: number;
+}
+
+/** 单个电子围栏的多边形点 */
+export type FencePolygonPoints = [number, number][];
+
+/** 单个电子围栏信息 */
+export interface SingleFence {
+  id: number;
+  label: string;
+  name: string;
+  polygon: FencePolygonPoints;
 }

+ 36 - 1
src/views/cameras/algo-params-setting/components/FenceEditorV2/utils.ts

@@ -1,6 +1,7 @@
 import { GROUP_NAME, defaultCircleStyle, defaultLineStyle } from './constants';
 import { uid } from 'uid';
 import { FenceGroup } from './types';
+import Konva from 'konva';
 
 export const getCircleConfig = (points: number[], scale: number) => {
   const circlePoints = [];
@@ -24,7 +25,8 @@ export const createCircleConfigItem = (point: [number, number], idx: number, sca
   };
 };
 
-export const createGroupConfig = (points: number[], scale: number): FenceGroup => {
+export const createGroupConfig = (data: { points: number[]; scale: number; fenceId?: number }): FenceGroup => {
+  const { points, scale, fenceId } = data;
   const lineConfig = {
     ...defaultLineStyle,
     strokeWidth: defaultLineStyle.strokeWidth / scale,
@@ -36,6 +38,39 @@ export const createGroupConfig = (points: number[], scale: number): FenceGroup =
     name: GROUP_NAME,
     circleConfigs,
     uid: uid(),
+    fenceId,
     _temp: { points: [] },
   };
 };
+
+/** 一个多边形一维点转化为二维的点 */
+export const polygonToPoint2 = (fenceGroup: Konva.Group): [number, number][] | null => {
+  const groupX = fenceGroup.x();
+  const groupY = fenceGroup.y();
+
+  const line = (fenceGroup as Konva.Group).findOne((x: any) => x.className === 'Line') as Konva.Line;
+  const points = line?.points();
+  /** 有些line对象存在,但是没有点坐标,所以要判断过滤一下 */
+  if (points && points.length > 0) {
+    const newPoints: [number, number][] = [];
+    /** 存到后端的时候,只给点的坐标信息,不会给group的位置信息,所以要将点的坐标加上group的位移,才是之后点的最终坐标 */
+    for (let i = 0; i < points.length; i += 2) {
+      newPoints.push([Math.floor(points[i] + groupX), Math.floor(points[i + 1] + groupY)]);
+    }
+    return newPoints;
+  }
+  return null;
+};
+
+/** 一个多边形一维点转化为二维的点 */
+export const polygonPoint1ToPoint2 = (points: number[]): [number, number][] | null => {
+  if (points.length < 2) {
+    console.error('多边形的点数量少于1个');
+    return null;
+  }
+  const newPoints: [number, number][] | null = [];
+  for (let i = 0; i < points.length; i += 2) {
+    newPoints.push([points[i], points[i + 1]]);
+  }
+  return newPoints;
+};

+ 15 - 0
src/views/cameras/algo-params-setting/components/FenceToolbar/FenceNameItem.vue

@@ -0,0 +1,15 @@
+<!-- 电子围栏名称的一项 -->
+<template>
+  <div :class="props.active ? 'active' : ''">{{ props.name }}</div>
+</template>
+
+<script lang="ts" setup>
+  const props = defineProps<{ name: string; id: number; active: boolean }>();
+</script>
+
+<style>
+  .active {
+    background: #409eff;
+    color: #fff;
+  }
+</style>

+ 36 - 13
src/views/cameras/algo-params-setting/components/FenceToolbar/FenceToolbar.vue

@@ -1,33 +1,46 @@
 <template>
-  <div class="toolbar">
-    <div class="fenceDrawingTip" v-if="isEdit">
-      {{ cameraAlgoStore.selectedAlgoDetail?.algoInfo?.name }}算法电子围栏绘制中
+  <div class="fenceWrapper">
+    <div>
+      <div>电子围栏</div>
+      <ElSwitch size="small" class="fenceSwitchBtn" />
     </div>
-    <template v-if="props.isEdit">
-      <!-- <ToolbarIcon
+    <PresetSelect />
+
+    <div style="display: flex"> <ElCheckbox label="检测围栏外部" /> <ElCheckbox label="前台画面显示" /> </div>
+    <div><FenceNameItem /></div>
+
+    <div class="toolbar">
+      <!-- <div class="fenceDrawingTip" v-if="isEdit">
+        {{ cameraAlgoStore.selectedAlgoDetail?.algoInfo?.name }}算法电子围栏绘制中
+      </div> -->
+      <template v-if="props.isEdit">
+        <!-- <ToolbarIcon
         :src="deleteIcon"
         :active="false"
         @click="emits('toggleFence')"
         tip="检测范围反选"
       /> -->
-      <ToggleFenceStatus @click="emits('toggleRange')" />
-      <ToolbarIcon :src="deleteIcon" :active="false" @click="emits('remove')" tip="删除电子围栏" />
-      <ToolbarIcon :src="saveIcon" :active="false" @click="emits('save')" tip="保存电子围栏" />
-    </template>
+        <ToggleFenceStatus @click="emits('toggleRange')" />
+        <ToolbarIcon :src="deleteIcon" :active="false" @click="emits('remove')" tip="删除电子围栏" />
+        <ToolbarIcon :src="saveIcon" :active="false" @click="emits('save')" tip="保存电子围栏" />
+      </template>
 
-    <ElButton type="primary" size="small" @click="toggleEdit">{{
-      props.isEdit ? '退出编辑' : '编辑电子围栏'
-    }}</ElButton>
+      <ElButton type="primary" size="small" @click="toggleEdit">{{
+        props.isEdit ? '退出编辑' : '编辑电子围栏'
+      }}</ElButton>
+    </div>
   </div>
 </template>
 <script setup lang="ts">
   import { defineEmits } from 'vue';
-  import { ElButton } from 'element-plus';
+  import { ElButton, ElSwitch } from 'element-plus';
   import ToolbarIcon from '../ToolbarIcon/ToolbarIcon.vue';
   import saveIcon from '@/assets/images/camera/save.png';
   import deleteIcon from '@/assets/images/camera/delete.png';
   import ToggleFenceStatus from './ToggleFenceStatus.vue';
   import useCameraAlgoStore from '../../store/useCameraAlgoStore';
+  import PresetSelect from '../PresetSelect/PresetSelect.vue';
+  import FenceNameItem from './FenceNameItem.vue';
 
   const cameraAlgoStore = useCameraAlgoStore();
 
@@ -64,4 +77,14 @@
     margin-right: 30px;
     color: #1890ff;
   }
+
+  .fenceSwitchBtn {
+    position: absolute;
+    right: 10px;
+    top: 10px;
+  }
+
+  .fenceWrapper {
+    padding: 15px;
+  }
 </style>

+ 12 - 15
src/views/cameras/algo-params-setting/store/useFenceStore.ts

@@ -1,21 +1,16 @@
-import {
-  GetFenceParams,
-  getFenceApi,
-  saveFenceApi,
-  SaveFenceParams,
-  editFenceApi,
-} from '@/api/camera/camera-preview';
+import { GetFenceParams, getFenceApi, saveFenceApi, SaveFenceParams, editFenceApi } from '@/api/camera/camera-preview';
 import { defineStore } from 'pinia';
 import { ref } from 'vue';
-import { ServerLines } from '../components/FenceEditor/constants';
+import { ServerLine, ServerLines } from '../components/FenceEditor/constants';
 import { ElMessage } from 'element-plus';
+import safeParse from '@/utils/safeParse';
 
 /** 当前电子围栏的store */
 export const useFenceStore = defineStore('electronicFencePolygonStore', () => {
   /** 后端返回的电子围栏点 */
   const serverFencePoints = ref<ServerLines>([]);
   /** 当前编辑的电子围栏的点 */
-  const currentFencePoints = ref([]);
+  const currentFencePoints = ref<ServerLines>([]);
   const currentFenceId = ref<number>();
   const loading = ref(false);
 
@@ -26,11 +21,13 @@ export const useFenceStore = defineStore('electronicFencePolygonStore', () => {
     return getFenceApi(param)
       .then((res) => {
         currentFenceId.value = res.id;
-        const points = res.electronicFencePolygon
-          ? (JSON.parse(res.electronicFencePolygon) as [])
-          : [];
-        currentFencePoints.value = points;
-        serverFencePoints.value = points;
+        const fence = res.electronicFence ? (JSON.parse(res.electronicFence) as []) : ([] as { polygon: string }[]);
+        // const pointsJSON = points.poly
+        const newFence = fence.map((x) => {
+          return { ...x, polygon: safeParse(x.polygon) as ServerLine };
+        }) as unknown as ServerLines;
+        currentFencePoints.value = newFence;
+        serverFencePoints.value = newFence;
       })
       .catch(() => {
         currentFenceId.value = undefined;
@@ -56,7 +53,7 @@ export const useFenceStore = defineStore('electronicFencePolygonStore', () => {
       return;
     }
     if (currentFenceId.value) {
-      return editFenceApi({ ...param, id: currentFenceId.value });
+      return editFenceApi({ ...param, fenceId: currentFenceId.value });
     }
     return saveFenceApi(param).then((res) => {
       console.log('save success', res);