Bläddra i källkod

完成云台相机

chauncey 1 år sedan
förälder
incheckning
68bc4172e6

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

@@ -326,12 +326,13 @@ export const editFenceApi = (data: UpdateFenceParams) => {
 interface CreatePresetParam {
   presetName: string;
   cameraId: number;
+  imageUrl: string;
 }
 
 /** 创建预置位 deprecated*/
 // export const createPresetApi = (data: CreatePresetParam) => {
 //   return http.request({
-//     url: `/onvif/createPreset`,
+//     url: `/admin/onvif/createPreset`,
 //     method: 'post',
 //     data,
 //   });
@@ -339,7 +340,7 @@ interface CreatePresetParam {
 /** 创建预置位 */
 export const createPresetApi = (data: CreatePresetParam) => {
   return http.request({
-    url: `/onvif/saveCameraPreset`,
+    url: `/admin/onvif/saveCameraPreset`,
     method: 'post',
     data,
   });
@@ -353,7 +354,7 @@ interface UpdatePresetNameParam {
 /** 修改预置位名称 */
 export const changePresetNameApi = (data: UpdatePresetNameParam) => {
   return http.request({
-    url: `/onvif/changePresetName`,
+    url: `/admin/onvif/changePresetName`,
     method: 'post',
     data,
   });
@@ -362,17 +363,17 @@ export const changePresetNameApi = (data: UpdatePresetNameParam) => {
 /** 删除预置位 deprecated*/
 // export const deletePresetApi = (data: { presetToken: string; cameraId: number }) => {
 //   return http.request({
-//     url: `/onvif/deletePreset`,
+//     url: `/admin/onvif/deletePreset`,
 //     method: 'post',
 //     data,
 //   });
 // };
 /** 删除预置位 */
-export const deletePresetApi = (data: { presetToken: string; cameraId: number }) => {
+export const deletePresetApi = (presetToken: string, cameraId: string) => {
   return http.request({
-    url: `/onvif/deletePreset`,
-    method: 'post',
-    data,
+    url: `/admin/onvif/deleteCameraPreset`,
+    method: 'delete',
+    data: { presetToken, cameraId },
   });
 };
 export interface PresetDetailItem {
@@ -393,17 +394,17 @@ export interface PresetDetailItem {
 
 export interface PresetListResp {
   cameraId: number;
-  id:number;
-  imageUrl:string;
-  presetName:string;
-  presetToken:string;
+  id: number;
+  imageUrl: string;
+  presetName: string;
+  presetToken: string;
 }
 
-/** 获取预置位列表  depracted*/ 
+/** 获取预置位列表  depracted*/
 // export const getPresetListApi = (cameraId: number) => {
 //   return http.request<PresetDetailItem[]>(
 //     {
-//       url: `/onvif/getPresets`,
+//       url: `/admin/onvif/getPresets`,
 //       // url: `/admin/algo/queryCameraPreset`,
 //       method: 'get',
 //       params: { cameraId },
@@ -416,7 +417,7 @@ export interface PresetListResp {
 /** 获取预置位列表 */
 export const getPresetListApi = (cameraId: number) => {
   return http.request<PresetListResp[]>({
-    url: `/onvif/queryCameraPresetList`,
+    url: `/admin/onvif/queryCameraPresetList`,
     method: 'get',
     params: { cameraId },
   });
@@ -425,12 +426,25 @@ export const getPresetListApi = (cameraId: number) => {
 /** 跳转到对应的预置位 */
 export const goToPresetApi = (data: { presetToken: string; cameraId: number }) => {
   return http.request({
-    url: `/onvif/gotoPreset`,
+    url: `/admin/onvif/gotoPreset`,
     method: 'post',
     data,
   });
 };
 
+/** 上传预置位图片 */
+export const uploadPresetImageApi = (file: Blob, bizType: string) => {
+  const formData = new FormData();
+  formData.append('bizType', bizType);
+  formData.append('file', file);
+  return http.request({
+    url: `/admin/minio/uploadFile`,
+    method: 'post',
+    data: formData,
+    headers: { 'Content-Type': 'multipart/form-data' },
+  });
+};
+
 export interface CameraAlgoPresetResp {
   algoId: string;
   algoCode: string;
@@ -459,7 +473,7 @@ interface CameraMoveParam {
 /** 移动相机 */
 export const cameraMoveApi = (data: CameraMoveParam) => {
   return http.request({
-    url: `/onvif/move`,
+    url: `/admin/onvif/move`,
     method: 'post',
     data,
   });

+ 26 - 7
src/components/LiveVideo/LiveVideo.vue

@@ -9,20 +9,14 @@
 </template>
 
 <script setup lang="ts">
-  import { onMounted, onBeforeUnmount, watch, ref, computed } from 'vue';
+  import { onMounted, onBeforeUnmount, watch, ref } from 'vue';
   import mpegts from 'mpegts.js';
   import { useUserStore } from '@/store/modules/user';
   import { storeToRefs } from 'pinia';
-  import useAuthStore from '@/store/modules/useAuth';
 
   const userStore = useUserStore();
   const { token } = storeToRefs(userStore);
 
-  const authStore = useAuthStore();
-  const { checkAuthValid } = authStore;
-
-  const restartNum = ref(0);
-
   let isVideoLoadingFailed = ref(false);
   const props = defineProps<{
     url: string;
@@ -40,6 +34,26 @@
     emit('timeUpdate', event.target.currentTime);
   };
 
+  // 添加截图方法
+  const captureImage = (): string => {
+    if (!videoRef.value) {
+      return '';
+    }
+    
+    const video = videoRef.value as HTMLVideoElement;
+    const canvas = document.createElement('canvas');
+    canvas.width = video.videoWidth;
+    canvas.height = video.videoHeight;
+    
+    const ctx = canvas.getContext('2d');
+    if (!ctx) {
+      return '';
+    }
+    
+    ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
+    return canvas.toDataURL('image/jpeg', 1.0);
+  };
+
   const initPlay = () => {
     if (!props.url || !videoRef.value) {
       return;
@@ -144,6 +158,11 @@
     destroyPlayer();
     clearInterval(interval);
   });
+
+  // 暴露截图方法供父组件使用
+  defineExpose({
+    captureImage
+  });
 </script>
 
 <style scoped>

+ 17 - 2
src/modules/algo-params-setting-base/components/CameraLiveVideo/CameraLiveVideo.vue

@@ -1,16 +1,18 @@
 <template>
-  <LiveVideo :url="videoUrl" v-if="videoUrl" />
+  <LiveVideo :url="videoUrl" v-if="videoUrl" ref="liveVideoRef" />
   <div v-if="!videoUrl" class="noPushStreamIpTip">暂无相机视频流地址</div>
 </template>
 <script lang="ts" setup>
   // import bg from '@/assets/images/camera/video-live.png';
   import LiveVideo from '@/components/LiveVideo/LiveVideo.vue';
   import { storeToRefs } from 'pinia';
-  import { computed } from 'vue';
+  import { computed, ref } from 'vue';
   import useCameraDetailStore from '../../store/useCameraDetailStore';
   const cameraDetailStore = useCameraDetailStore();
   const skyeyeVideoPath = localStorage.getItem('skyeyeVideoPath');
   const { detail } = storeToRefs(cameraDetailStore);
+  
+  const liveVideoRef = ref<InstanceType<typeof LiveVideo> | null>(null);
 
   const videoUrl = computed(() => {
     if (detail.value?.render) {
@@ -26,6 +28,19 @@
       return detail?.value?.pushStreamDTO?.videoUrls?.pushstreamIp;
     }
   });
+  
+  // 暴露截图方法
+  const captureImage = (): string => {
+    if (!liveVideoRef.value) {
+      return '';
+    }
+    return liveVideoRef.value.captureImage();
+  };
+  
+  // 暴露方法供父组件使用
+  defineExpose({
+    captureImage
+  });
 </script>
 <style>
   .noPushStreamIpTip {

+ 27 - 6
src/views/cameras/overview/components/CameraOverviewPopover.vue

@@ -12,12 +12,12 @@
     <div class="camera-overview-popover--custom__content">
       <main class="main">
         <div class="cameraVideo">
-          <CameraLiveVideo />
+          <CameraLiveVideo ref="cameraLiveVideoRef" />
         </div>
         <div class="presetAddWrapper" v-if="!!overviewCameraData.isPtz">
           <CameraViewScale @update:ControlPerspective="activePresetToken = ''" />
           <CameraDirectionControl @update:ControlPerspective="activePresetToken = ''" />
-          <ElButton type="primary" style="margin-top: 20px; width: 100px" @click="handleAddPreset">添加预置位</ElButton>
+          <ElButton type="primary" style="margin-top: 20px; width: 100px" @click="handleAddPreset" v-show="displayPresetList.length < 10">添加预置位</ElButton>
         </div>
       </main>
       <footer class="footer" v-if="!!overviewCameraData.isPtz && presetList.length > 0">
@@ -39,7 +39,12 @@
             :key="item.presetToken"
             :class="{ 'active-preset': activePresetToken === item.presetToken }"
           >
-            <img :src="item.imageUrl || PresetPositionItem" alt="预置位" style="cursor: pointer" @click="handleGoToPreset(item)" />
+            <img
+              :src="item.imageUrl || PresetPositionItem"
+              alt="预置位"
+              style="cursor: pointer"
+              @click="handleGoToPreset(item)"
+            />
             <img
               v-if="isEditMode"
               :src="DeletePresetPositionIcon"
@@ -63,12 +68,14 @@
   import DeletePresetPositionIcon from '@/assets/icons/delete-preset-position.svg';
   import PresetPositionItem from '@/assets/icons/preset-placeholder-img.svg';
   import EditPresetPositionFocusIcon from '@/assets/icons/edit-preset-position-focus.svg';
+  import { dataURLtoBlob } from '@/utils/file/base64Conver';
   import {
     getPresetListApi,
     PresetListResp,
     deletePresetApi,
     createPresetApi,
     goToPresetApi,
+    uploadPresetImageApi,
   } from '@/api/camera/camera-preview';
   import CameraLiveVideo from '@/modules/algo-params-setting-base/components/CameraLiveVideo/CameraLiveVideo.vue';
   import CameraViewScale from '@/modules/algo-params-setting-base/components/CameraViewSetting/CameraViewScale.vue';
@@ -81,6 +88,7 @@
   const displayPresetList = ref<PresetListResp[]>([]);
   const isEditMode = ref(false);
   const activePresetToken = ref<string>(''); // 当前激活的预置位token
+  const cameraLiveVideoRef = ref<InstanceType<typeof CameraLiveVideo> | null>(null); // 添加对CameraLiveVideo的引用并指定正确类型
 
   const props = defineProps<{
     dialogVisible: boolean;
@@ -138,9 +146,9 @@
           cancelButtonText: '取消',
         },
       ).then(async () => {
-        await deletePresetApi({ presetToken: item.presetToken, cameraId });
+        await deletePresetApi(item.presetToken, String(cameraId));
         ElMessage.success('删除成功');
-        displayPresetList.value.splice(index, 1);
+        await getPresetList();
         const currentPageStartIdx = (currentPage.value - 1) * pageSize;
         if (currentPageStartIdx >= displayPresetList.value.length && currentPage.value > 1) {
           currentPage.value--;
@@ -167,7 +175,20 @@
     }).then(async ({ value }) => {
       const cameraId = props.overviewCameraData.id;
       if (!cameraId) return;
-      const res = await createPresetApi({ presetName: value, cameraId });
+
+      let imageBase64 = '';
+      if (cameraLiveVideoRef.value) {
+        imageBase64 = cameraLiveVideoRef.value.captureImage();
+      }
+
+      // 未来可以在这里把imageBase64传给后端
+      const blob = dataURLtoBlob(imageBase64);
+      const url = await uploadPresetImageApi(blob, 'CAMERA_IMAGE');
+      if (!url) {
+        ElMessage.error('上传预置位图片失败');
+        return;
+      }
+      const res = await createPresetApi({ presetName: value, cameraId, imageUrl: url.url });
       if (res) {
         ElMessage.success('添加预置位成功');
         await getPresetList();