Explorar o código

增加了相机选择分辨率的功能,同时电子围栏也一起改变尺寸大小

louhangfei %!s(int64=2) %!d(string=hai) anos
pai
achega
10db136aa1

+ 31 - 0
src/api/camera/camera-preview.ts

@@ -264,3 +264,34 @@ export const cameraMoveApi = (data: CameraMoveParam) => {
     data,
   });
 };
+
+interface SaveCameraParams {
+  startTime: string;
+  endTime: string;
+  imageResolution: string;
+  recordPeriod: number;
+  /** 这个要调整一下 */
+  reservation: string;
+  cameraId: number;
+}
+
+export const saveCameraParamsApi = (data: SaveCameraParams) => {
+  localStorage.setItem('cameraParams' + data.cameraId, JSON.stringify(data));
+  return Promise.resolve();
+};
+
+export const getCameraParamsApi = (cameraId: number) => {
+  const data = localStorage.getItem('cameraParams' + cameraId);
+  let jsonData = {
+    startTime: '',
+    endTime: '',
+    imageResolution: '1920',
+    recordPeriod: 20,
+    /** 这个要调整一下 */
+    reservation: '10',
+  };
+  if (data) {
+    jsonData = JSON.parse(data);
+  }
+  return Promise.resolve(jsonData);
+};

+ 6 - 3
src/utils/dateUtil.ts

@@ -1,12 +1,15 @@
-import dayjs from 'dayjs';
+import dayjs, { Dayjs } from 'dayjs';
 
 const DATE_TIME_FORMAT = 'YYYY-MM-DD HH:mm';
 const DATE_FORMAT = 'YYYY-MM-DD ';
 
-export function formatToDateTime(date: Date, formatStr = DATE_TIME_FORMAT): string {
+export function formatToDateTime(
+  date: Date | Dayjs | string,
+  formatStr = DATE_TIME_FORMAT,
+): string {
   return dayjs(date).format(formatStr);
 }
 
-export function formatToDate(date: Date, formatStr = DATE_FORMAT): string {
+export function formatToDate(date: Date | Dayjs | string, formatStr = DATE_FORMAT): string {
   return dayjs(date).format(formatStr);
 }

+ 1 - 1
src/views/cameras/preview/components/AlgorithmsSetting/AlgorithmsSetting.vue

@@ -26,7 +26,7 @@
   </div>
 </template>
 <script lang="ts" setup>
-  import { onMounted, watch } from 'vue';
+  import { onMounted } from 'vue';
   import useCameraAlgoStore from '../../store/useCameraAlgoStore';
   import AlgoSettingCard from './AlgoSettingCard.vue';
   import { storeToRefs } from 'pinia';

+ 14 - 3
src/views/cameras/preview/components/CameraLiveVideo/CameraLiveVideo.vue

@@ -1,12 +1,23 @@
 <template>
-  <img :src="bg" alt="" class="videoLive" />
+  <img
+    :src="bg"
+    alt=""
+    class="videoLive"
+    :style="{
+      width: `${cameraDetailStore.videoSize.width}px`,
+      height: `${cameraDetailStore.videoSize.height}px`,
+    }"
+  />
 </template>
 <script lang="ts" setup>
   import bg from '@/assets/images/camera/video-live.png';
+
+  import useCameraDetailStore from '../../store/useCameraDetailStore';
+  const cameraDetailStore = useCameraDetailStore();
 </script>
 <style scoped>
   .videoLive {
-    width: 720px;
-    height: 405px;
+    /* width: 720px;
+    height: 405px; */
   }
 </style>

+ 60 - 53
src/views/cameras/preview/components/CameraParams/CameraParams.vue

@@ -1,79 +1,86 @@
-<script lang="ts" setup>
-  import { watch, ref } from 'vue';
-
-  const props = defineProps<{
-    detail: any;
-  }>();
-  const cameraParams = ref({
-    imageResolution: '',
-    recordPeriod: null,
-    startTime: '',
-    endTime: '',
-    reservation: '',
-  });
-
-  //   声明时赋初始值
-  //   const form = reactive({
-  //     imageResolution: props.cameraInits.imageResolution,
-  //     recordPeriod: props.cameraInits.recordPeriod,
-  //     startTime: props.cameraInits.startTime,
-  //     endTime: props.cameraInits.endTime,
-  //     reservation: props.cameraInits.reservation,
-  //   });
-
-  watch(
-    () => {
-      return props.detail;
-    },
-    () => {
-      cameraParams.value = props.detail;
-    },
-    {
-      immediate: true,
-    },
-  );
-
-  const emits = defineEmits(['submit']);
-  const onSubmit = () => {
-    emits('submit', cameraParams);
-  };
-</script>
 <template>
-  <el-form :model="cameraParams" label-width="130px" lable-position="left">
+  <el-form :model="cameraDetailStore" label-width="130px" lable-position="left">
     <el-form-item label="分辨率:">
-      <el-select v-model="cameraParams.imageResolution" style="width: 100%">
-        <el-option label="1920" value="1920" />
-        <el-option label="1280" value="1280" />
-        <el-option label="720" value="720" />
+      <el-select
+        v-model="cameraDetailStore.params.imageResolution"
+        style="width: 100%"
+        size="small"
+      >
+        <el-option
+          v-for="x in videoResolutionList"
+          :label="x.label"
+          :value="x.value"
+          :key="x.value"
+        />
       </el-select>
     </el-form-item>
     <el-form-item label="录制周期:">
-      <el-select v-model="cameraParams.recordPeriod" style="width: 100%">
-        <el-option label="10天" value="10天" />
-        <el-option label="5天" value="5天" />
-        <el-option label="1天" value="1天" />
+      <el-select v-model="cameraDetailStore.params.recordPeriod" style="width: 100%" size="small">
+        <el-option label="10天" :value="10" />
+        <el-option label="5天" :value="5" />
+        <el-option label="1天" :value="1" />
       </el-select>
     </el-form-item>
     <el-form-item label="录制时间:">
       <el-col :span="11">
-        <el-time-picker v-model="cameraParams.startTime" style="width: 100%" />
+        <el-time-picker
+          v-model="cameraDetailStore.params.startTime"
+          style="width: 100%"
+          size="small"
+        />
       </el-col>
       <el-col :span="1">
         <span class="text-center">-</span>
       </el-col>
       <el-col :span="11">
-        <el-time-picker v-model="cameraParams.endTime" style="width: 100%" />
+        <el-time-picker
+          v-model="cameraDetailStore.params.endTime"
+          style="width: 100%"
+          size="small"
+        />
       </el-col>
     </el-form-item>
     <el-form-item label="返回预置位:">
-      <el-input v-model="cameraParams.reservation" />
+      <el-input v-model="cameraDetailStore.params.reservation" size="small" />
     </el-form-item>
     <el-form-item>
-      <el-button type="primary" @click="onSubmit" disabled>保存</el-button>
+      <el-button type="primary" @click="onSubmit">保存</el-button>
     </el-form-item>
   </el-form>
 </template>
 
+<script lang="ts" setup>
+  import { getCameraParamsApi, saveCameraParamsApi } from '@/api/camera/camera-preview';
+  import { formatToDateTime } from '@/utils/dateUtil';
+  import { ElMessage } from 'element-plus';
+  import useCameraDetailStore from '../../store/useCameraDetailStore';
+  import { onMounted } from 'vue';
+  import { videoResolutionList } from './types';
+
+  const cameraDetailStore = useCameraDetailStore();
+
+  onMounted(() => {
+    getCameraParamsApi(cameraDetailStore.cameraId).then((res) => {
+      cameraDetailStore.params = res;
+    });
+  });
+
+  const onSubmit = () => {
+    const params = cameraDetailStore.params;
+    const DATE_TIME_STR = 'YYYY-MM-DD HH:mm:ss';
+    const endTime = formatToDateTime(params.endTime, DATE_TIME_STR);
+    const startTime = formatToDateTime(params.startTime, DATE_TIME_STR);
+    saveCameraParamsApi({
+      ...params,
+      startTime,
+      endTime,
+      cameraId: cameraDetailStore.cameraId,
+    }).then((res) => {
+      ElMessage.success('保存成功');
+    });
+  };
+</script>
+
 <style scoped>
   .text-center {
     /* text-align: center; */

+ 10 - 0
src/views/cameras/preview/components/CameraParams/types.ts

@@ -0,0 +1,10 @@
+/** 分辨率的枚举值 */
+export enum VideoResolution {
+  '1920*1080' = 1,
+  '720*405' = 0.375,
+}
+
+export const videoResolutionList = [
+  { label: '1920*1080', value: VideoResolution['1920*1080'] },
+  { label: '720*405', value: VideoResolution['720*405'] },
+];

+ 23 - 9
src/views/cameras/preview/components/CameraViewSetting/CameraViewSetting.vue

@@ -12,10 +12,20 @@
       <PresetSelect />
     </div>
 
-    <div class="cameraViewSettingWrapper">
-      <FenceEditor ref="fenceEditorRef" />
-
-      <div class="cameraVideo"><CameraLiveVideo /></div>
+    <div
+      class="cameraViewSettingWrapper"
+      :style="{
+        width: `${cameraDetailStore.videoSize.width}px`,
+        height: `${cameraDetailStore.videoSize.height}px`,
+      }"
+    >
+      <FenceEditor
+        ref="fenceEditorRef"
+        :width="cameraDetailStore.videoSize.width"
+        :height="cameraDetailStore.videoSize.height" />
+      <div class="cameraVideo">
+        <CameraLiveVideo />
+      </div>
       <div class="presetAddWrapper" :class="{ hidePresetControlCls: isEdit }">
         <CameraDirectionControl />
         <ElButton
@@ -34,7 +44,7 @@
   </div>
   <div class="cameraParamsSettingWrapper">
     <div class="cameraParamsSetting">
-      <CameraParams :detail="cameraParamsDetail" />
+      <CameraParams />
     </div>
     <div class="algorithmsSetting"> <AlgorithmsSetting /> </div>
   </div>
@@ -70,8 +80,6 @@
 
   const addPresetModalVisible = ref(false);
 
-  const cameraParamsDetail = ref({});
-
   const handleClose = () => {
     addPresetModalVisible.value = false;
   };
@@ -179,12 +187,18 @@
       presetStore.getPresetList(cameraId);
     }
   });
+
+  // watchEffect(() => {
+  //   const scale = cameraDetailStore.params.imageResolution;
+  //   console.log('scale change', scale);
+  //   fenceEditorRef.value?.setScale(scale);
+  // });
 </script>
 <style scoped>
   .cameraViewSettingWrapper {
     position: relative;
-    width: 720px;
-    height: 405px;
+    /* width: 720px;
+    height: 405px; */
     border: 1px solid #ccc;
   }
 

+ 24 - 10
src/views/cameras/preview/components/FenceEditor/FenceEditor.vue

@@ -4,7 +4,7 @@
 
 <script lang="ts" setup>
   import Konva from 'konva';
-  import { ref, onMounted, onUnmounted } from 'vue';
+  import { ref, onMounted, onUnmounted, watch } from 'vue';
   import { GROUP_NAME, POLYGON_NAME, Points, ToolObjectItem, toolObject } from './constants';
   import { ElMessage } from 'element-plus';
   import { getDefaultScale } from './utils';
@@ -37,13 +37,22 @@
     stage?.destroy();
   });
 
+  const props = defineProps<{ width: number; height: number }>();
+
+  watch(
+    () => [props.width, props.height],
+    () => {
+      initKonvaStage();
+    },
+  );
+
   /**
    *初始化konva舞台
    */
   function initKonvaStage() {
     //1实例化stage层
-    stageWidth = mapRef.value?.clientWidth || 0;
-    stageHeight = mapRef.value?.clientHeight || 0;
+    stageWidth = props.width || 0;
+    stageHeight = props.height || 0;
     console.log('stageWidth', stageWidth);
     stage = new Konva.Stage({
       container: 'editorMap',
@@ -52,9 +61,9 @@
       ignoreStroke: true,
       background: '#00ff00',
     });
-    // if (import.meta.env.MODE === 'development') {
-    //   window.stage = stage;
-    // }
+    if (import.meta.env.MODE === 'development') {
+      window.stage = stage;
+    }
     setStageCursor('pointer');
 
     //2实例化layer层
@@ -506,10 +515,10 @@
     layer?.draw();
   }
   /**
- *多边形
-  @param currentTool
-  * @param points 多边形绘画的各个顶点,类型数组
-  */
+  *多边形
+   @param currentTool
+   * @param points 多边形绘画的各个顶点,类型数组
+   */
   function drawPolygon(currentTool: ToolObjectItem, points: number[], group: Konva.Group) {
     let poly = new Konva.Line({
       name: currentTool.name + 'poly',
@@ -677,6 +686,10 @@
     layer?.removeChildren();
   };
 
+  const setScale = (scale: number) => {
+    stage?.setAttr('scaleX', scale);
+  };
+
   defineExpose({
     remove: removeCurrent,
     toObject,
@@ -686,6 +699,7 @@
     exitEditMode,
     setEditMode,
     clear,
+    setScale,
   });
 </script>
 

+ 2 - 46
src/views/cameras/preview/components/FenceToolbar/FenceToolbar.vue

@@ -1,12 +1,5 @@
 <template>
   <div class="toolbar">
-    <!-- <ElButton>编辑</ElButton>
-    <ElButton @click="remove">删除</ElButton>
-    <ElButton @click="toObject">保存到localStorage</ElButton>
-    <ElButton @click="loadGroup">从local加载group</ElButton>
-    <ElButton @click="toRawObject">保存Raw</ElButton> -->
-    <ToolbarIcon :src="mousePointerIcon" :active="false" />
-    <!-- <ToolbarIcon :src="editIcon" :active="false" @click="emits('setEditable')" /> -->
     <ToolbarIcon :src="deleteIcon" :active="false" @click="emits('remove')" />
     <ToolbarIcon :src="saveIcon" :active="false" @click="emits('save')" />
     <ElButton type="primary" size="small" @click="toggleEdit">{{
@@ -15,57 +8,20 @@
   </div>
 </template>
 <script setup lang="ts">
-  import { ref, defineEmits, watch } from 'vue';
-  import { ElButton, ElSwitch } from 'element-plus';
-  import PolygonEditor from '../FenceEditor/FenceEditor.vue';
-  import { ServerLines } from '../FenceEditor/constants';
+  import { defineEmits } from 'vue';
+  import { ElButton } 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 mousePointerIcon from '@/assets/images/camera/mousePointer.png';
-  import editIcon from '@/assets/images/camera/pen.png';
-  import useFenceStore from '../../store/useFenceStore';
 
-  const isFenceOn = ref(true);
   const props = defineProps<{ isEdit: boolean }>();
 
-  const polygonEditorRef = ref<typeof PolygonEditor | null>(null);
-
-  const fenceStore = useFenceStore();
   const emits = defineEmits<{
     (e: 'toggleEditable', editState: boolean): unknown;
     (e: 'remove'): unknown;
     (e: 'save'): unknown;
   }>();
 
-  const toObject = () => {
-    const json = polygonEditorRef.value?.toObject();
-    console.log('toObject json', json);
-    localStorage.setItem('mapDataV2', JSON.stringify(json));
-  };
-
-  const toRawObject = () => {
-    const objects = polygonEditorRef.value?.toRawObject();
-    console.log('objects', objects);
-    localStorage.setItem('mapData', JSON.stringify(objects));
-  };
-
-  const loadGroup = () => {
-    const data = localStorage.getItem('mapDataV2');
-    console.log('loadGroup data', data);
-    if (!data) return;
-    const dataJSON = JSON.parse(data) as ServerLines;
-    const groups = dataJSON;
-    const rawLinePoints = groups[0];
-    const points: number[] = [];
-    rawLinePoints.forEach((line) => {
-      points.push(line[0], line[1]);
-    });
-    console.log('points', points);
-
-    polygonEditorRef.value?.createLines(points);
-  };
-
   const toggleEdit = () => {
     emits('toggleEditable', !props.isEdit);
   };

+ 4 - 1
src/views/cameras/preview/store/useCameraAlgoStore.ts

@@ -27,7 +27,10 @@ const useCameraAlgoStore = defineStore('cameraAlgo', () => {
   /** 所有算法列表中选定的算法id */
   const selectedAlgoId = ref<number>();
 
-  const selectedAlgoDetail = ref<CameraAlgoItemInCard>({} as CameraAlgoItemInCard);
+  const selectedAlgoDetail = ref<CameraAlgoItemInCard>({
+    // 复杂结构要加这个,否则v-model的时候会报错
+    detectionJSON: { detectionNum: 0, detectionUnit: 1 },
+  } as CameraAlgoItemInCard);
 
   const getAlgoDetail = (algoId: number): null | CameraAlgoItem => {
     const detail = cameraAlgoList.value?.find((x) => x.algoId === algoId);

+ 31 - 2
src/views/cameras/preview/store/useCameraDetailStore.ts

@@ -3,17 +3,46 @@
 import { CameraDetailServer } from '@/api/camera/camera-overview';
 import { useRouteQuery } from '@vueuse/router';
 import { defineStore } from 'pinia';
-import { ref } from 'vue';
+import { computed, ref } from 'vue';
+
+interface CameraParams {
+  startTime: string;
+  endTime: string;
+  imageResolution: number;
+  recordPeriod: number;
+  /** 这个要调整一下 */
+  reservation: string;
+}
+
+/** 宽/长的比例 */
+export const WIDTH_HEIGHT_RATIO = 0.5625;
+/** 分辨率以1920为基础 */
+export const BASE_RESOLUTION = 1920;
 
 const useCameraDetailStore = defineStore('cameraDetail', () => {
   const cameraId = useRouteQuery('cameraId', '', { transform: (str) => Number(str) });
 
   const detail = ref<CameraDetailServer | null>(null);
 
+  /** 参数设置 */
+  const params = ref<CameraParams>({
+    startTime: '',
+    endTime: '',
+    imageResolution: 1,
+    recordPeriod: 0,
+    reservation: '',
+  });
+
+  const videoSize = computed(() => {
+    const width = params.value.imageResolution * BASE_RESOLUTION;
+    const height = width * WIDTH_HEIGHT_RATIO;
+    return { width, height };
+  });
+
   const setDetail = (newDetail: CameraDetailServer) => {
     detail.value = newDetail;
   };
-  return { detail, setDetail, cameraId };
+  return { detail, setDetail, cameraId, params, videoSize };
 });
 
 export default useCameraDetailStore;