Procházet zdrojové kódy

Merge branch 'all-v4-chauncey' into 'all-v4'

相机布局完成

See merge request skyeye/skyeye_frontend/skyeye-admin!234
Fei Liu před 1 rokem
rodič
revize
8c3a183c26

+ 6 - 7
src/api/camera/camera-overview.ts

@@ -102,9 +102,9 @@ export const deleteCameraItems = (data: number[]) => {
 };
 
 /**
- * v4:根据相机code查询相机联网状态
- * @param data 
- * @returns 
+ * 通过codes获取相机状态。后期会废弃。建议使用 ids 获取
+ * @param data 
+ * @returns
  */
 export const getCameraStateByCodes = (data: { cameraCodeList: string[] }) => {
   return http.request<CameraStatus[]>({
@@ -114,13 +114,12 @@ export const getCameraStateByCodes = (data: { cameraCodeList: string[] }) => {
   });
 };
 
-
 /**
  * v4: 根据相机 id 查询相机联网状态
- * @param data 
- * @returns 
+ * @param data
+ * @returns
  */
-export const getCameraStateByIds = (data: { cameraIdList: number[] }) => {
+export const getCameraStateByIds = (data: { cameraIdList: number[] | string[] }) => {
   return http.request<CameraStatus[]>({
     url: '/admin/cameraConfig/queryCameraStatusListByCameraIds',
     method: 'post',

+ 23 - 1
src/api/scene/scene.ts

@@ -434,7 +434,29 @@ export function getPcCompanyLayoutListApi(params: { companyIds: number[] }) {
 export function getMobileCompanyLayoutList(params: { companyIds: number[] }) {
   return http.request<CompanyLayoutInfoList[]>({
     url: '/admin/homepageConfig/getMobileCompanyLayoutList',
-    method: 'get',
+    method: 'post',
+    params,
+  });
+}
+/**
+ * @description 查询相机主页布局列表-PC
+ * @author chauncey
+ */
+export function getPcCameraLayoutList(params: { workshopIdList: number[] }) {
+  return http.request<CompanyLayoutInfoList[]>({
+    url: '/admin/homepageConfig/getPcWorkshopLayoutList',
+    method: 'post',
+    params,
+  });
+}
+/**
+ * @description 查询相机主页布局列表-Mobile
+ * @author chauncey
+ */
+export function getMobileCameraLayoutList(params: { workshopIdList: number[] }) {
+  return http.request<CompanyLayoutInfoList[]>({
+    url: '/admin/homepageConfig/getMobileWorkshopLayoutList',
+    method: 'post',
     params,
   });
 }

+ 2 - 0
src/types/scene-layout/type.ts

@@ -1,6 +1,8 @@
 export interface CompanyInfoList {
   id: number;
   name: string;
+}
+export interface LayoutInfoList extends CompanyInfoList {
   layout: string;
   imgUrl: string;
 }

+ 39 - 8
src/views/cameras/preview/store/useCameraStatus.ts

@@ -1,4 +1,4 @@
-import { getCameraStateByCodes } from '@/api/camera/camera-overview';
+import { getCameraStateByCodes, getCameraStateByIds } from '@/api/camera/camera-overview';
 import { ref } from 'vue';
 
 /** 轮询相机异常的频率,5s */
@@ -7,23 +7,52 @@ const INTERVAL_TIME = 5000;
 export const useCameraStatus = () => {
   const noNetworkingNum = ref<number>(0);
   let interval;
-
-  const getState = (codeList, callback) => {
+  /**
+   * v3 旧获取方式,通过codes 获取。后期会废弃
+   * @description
+   */
+  const getStateByCodes = (codeList, callback) => {
     if (codeList.length === 0) return;
     getCameraStateByCodes({ cameraCodeList: codeList }).then((res) => {
-      const filterData = res.filter((item) => item.status === 1);
+      const filterData = res.filter((item) => item.networkingState === 1);
       noNetworkingNum.value = filterData.length;
       callback(res);
     });
   };
-
+  /**
+   * v4 接口,通过 ids 获取状态
+   * @description
+   */
+  const getStateByIds = (idList, callback) => {
+    if (idList.length === 0) return;
+    getCameraStateByIds({ cameraIdList: idList }).then((res) => {
+      const filterData = res.filter((item) => item.networkingState === 1);
+      noNetworkingNum.value = filterData.length;
+      callback(res);
+    });
+  };
+  /**
+   * @description --deprecated
+   */
   const openInterval = (codeList, callback) => {
     clearInterval(interval);
     setTimeout(() => {
-      getState(codeList, callback);
+      getStateByCodes(codeList, callback);
+    }, 500);
+    interval = setInterval(() => {
+      getStateByCodes(codeList, callback);
+    }, INTERVAL_TIME);
+  };
+  /**
+   * @description --new
+   */
+  const openIntervalNew = (idList, callback) => {
+    clearInterval(interval);
+    setTimeout(() => {
+      getStateByIds(idList, callback);
     }, 500);
     interval = setInterval(() => {
-      getState(codeList, callback);
+      getStateByIds(idList, callback);
     }, INTERVAL_TIME);
   };
 
@@ -34,8 +63,10 @@ export const useCameraStatus = () => {
   return {
     noNetworkingNum,
     openInterval,
+    openIntervalNew,
     closeInterval,
-    getState,
+    getStateByCodes,
+    getStateByIds,
   };
 };
 

+ 459 - 475
src/views/map-config/mini-map/MapBase/KonvaMap.vue

@@ -3,522 +3,506 @@
     <v-stage ref="stageAll" :config="stageConfig" @click="handleStageClick">
       <v-layer ref="layer">
         <v-image :config="bgConfig" v-show="bgImgUrl" />
-        <v-group
-          v-for="camera in cameras"
-          :key="camera.id"
-          :id="camera.id"
-          :config="camera.groupConfig"
-          @click="handleCameraClick(camera)"
-          @mouseover="(e) => handleMouseOver(e)"
-          @mouseleave="handleMouseLeave()"
-          @dragstart="handleDragStart()"
-        >
+        <v-group v-for="camera in cameras" :key="camera.id" :id="camera.id" :config="camera.groupConfig"
+          @click="handleCameraClick(camera)" @mouseover="(e) => handleMouseOver(e)" @mouseleave="handleMouseLeave()"
+          @dragstart="handleDragStart()">
           <v-image :config="camera.config" />
           <v-image v-if="camera.isDefault" ref="defaultIcon" :config="defaultIconConfig" />
         </v-group>
         <v-transformer :config="transformerConfig" ref="transformer" />
       </v-layer>
     </v-stage>
-    <div
-      v-show="defaultShow"
-      class="opt-container"
-      :style="{ position: 'absolute', left: posX + 'px', top: posY + 'px' }"
-    >
-      <div class="opt-item" :class="{ disabled: disabledSet }" @click="setDefaultCamera"
-        >设为默认相机</div
-      >
+    <div v-show="defaultShow" class="opt-container"
+      :style="{ position: 'absolute', left: posX + 'px', top: posY + 'px' }">
+      <div class="opt-item" :class="{ disabled: disabledSet }" @click="setDefaultCamera">设为默认相机</div>
       <div class="opt-item" @click="previewCamera">预览相机</div>
     </div>
 
-    <CameraPreview
-      v-if="isShow"
-      :last-pos-x="posX!"
-      :last-pos-y="posY!"
-      :video-url="videoUrl"
-      @close="closePreview"
-    />
-
-    <DefaultTip
-      v-show="tipShow"
-      :position="pos"
-      :style="{ position: 'absolute', left: posTipX + 'px', top: posTipY + 'px' }"
-    />
+    <CameraPreview v-if="isShow" :last-pos-x="posX!" :last-pos-y="posY!" :video-url="videoUrl" @close="closePreview" />
+
+    <DefaultTip v-show="tipShow" :position="pos"
+      :style="{ position: 'absolute', left: posTipX + 'px', top: posTipY + 'px' }" />
   </div>
 </template>
 
 <script setup lang="ts">
-  import { ref, onMounted, onBeforeUnmount, watch } from 'vue';
-  import { ElMessage, ElMessageBox } from 'element-plus';
-  import DefaultTip from '../components/DefaultTip.vue';
-  import cameraImgSrc from '@/assets/camera/cameraImg.png';
-  import favoritesImgSrc from '@/assets/camera/favorites.png';
-  import { TipPositionEnum, camerasGroupType } from '../type';
-  import { cloneDeep } from 'lodash-es';
-  import useMiniMap from '../use-mini-map';
-  import { storeToRefs } from 'pinia';
-  import { updateMinMapViewLayoutApi } from '@/api/scene/scene';
-  import CameraPreview from './CameraPreview.vue';
-  import { ShopMapCamera } from '@/types/scene/type'
-
-  const miniMap = useMiniMap();
-  const { shopCameraList } = storeToRefs(miniMap);
-
-  const emit = defineEmits(['changeDefaultCamera', 'sendCameraId', 'change']);
-  const props = defineProps<{ filterData: ShopMapCamera[] }>();
-  const camImg = new Image();
-
-  const stageConfig = ref({
-    width: 800,
-    height: 600,
-  });
-
-  const bgImg = new Image();
-  const favoritesImg = new Image();
-  favoritesImg.src = favoritesImgSrc;
-
-  //右键点击是否出现
-  const defaultShow = ref<boolean>(false);
-  const posX = ref<number>();
-  const posY = ref<number>();
-  const posTipX = ref<number>();
-  const posTipY = ref<number>();
-  const pos = ref(TipPositionEnum.TOP);
-  //上一次点击的相机
-  const lastClickedGroupId = ref<string | null>(null);
-  const lastClickedVideoUrl = ref<string | null>(null);
-
-  const bgImgUrl = ref<string | null>('');
-  const cameras = ref<camerasGroupType[]>([]);
-  //默认相机id
-  const defaultCameraId = ref('');
-  const tipShow = ref(false);
-  const disabledSet = ref(false);
-  const videoUrl = ref<string>('');
-
-  const cameraIconSize = { width: 52, height: 52 };
-
-  //标签
-  const layer = ref();
-  const transformer = ref();
-  const defaultIcon = ref();
-
-  function addIntegrationState(data: camerasGroupType[], updateData: ShopMapCamera[]) {
-    for (let i = 0; i < data.length; i++) {
-      const camera = data[i];
-      const matchedCamera = updateData.find((item) => item.code === camera.id);
-      if (matchedCamera) {
-        camera.groupConfig.visible = matchedCamera.integrationState === 0 ? true : false;
-      }
+import { ref, onMounted, onBeforeUnmount, watch } from 'vue';
+import { ElMessage, ElMessageBox } from 'element-plus';
+import DefaultTip from '../components/DefaultTip.vue';
+import cameraImgSrc from '@/assets/camera/cameraImg.png';
+import favoritesImgSrc from '@/assets/camera/favorites.png';
+import { TipPositionEnum, camerasGroupType } from '../type';
+import { cloneDeep } from 'lodash-es';
+// import useMiniMap from '../use-mini-map';
+// import { storeToRefs } from 'pinia';
+import { updateMinMapViewLayoutApi } from '@/api/scene/scene';
+import CameraPreview from './CameraPreview.vue';
+import { ShopMapCamera } from '@/types/scene/type'
+
+// const miniMap = useMiniMap();
+// const { shopCameraList } = storeToRefs(miniMap);
+
+const emit = defineEmits(['changeDefaultCamera', 'sendCameraId', 'change']);
+const props = defineProps<{ filterData: ShopMapCamera[], cameraList: ShopMapCamera[] }>();
+const camImg = new Image();
+
+const stageConfig = ref({
+  width: 800,
+  height: 600,
+});
+
+const bgImg = new Image();
+const favoritesImg = new Image();
+favoritesImg.src = favoritesImgSrc;
+
+//右键点击是否出现
+const defaultShow = ref<boolean>(false);
+const posX = ref<number>();
+const posY = ref<number>();
+const posTipX = ref<number>();
+const posTipY = ref<number>();
+const pos = ref(TipPositionEnum.TOP);
+//上一次点击的相机
+const lastClickedGroupId = ref<string | null>(null);
+const lastClickedVideoUrl = ref<string | null>(null);
+
+const bgImgUrl = ref<string | null>('');
+const cameras = ref<camerasGroupType[]>([]);
+//默认相机id
+const defaultCameraId = ref('');
+const tipShow = ref(false);
+const disabledSet = ref(false);
+const videoUrl = ref<string>('');
+
+const cameraIconSize = { width: 52, height: 52 };
+
+//标签
+const layer = ref();
+const transformer = ref();
+const defaultIcon = ref();
+
+function addIntegrationState(data: camerasGroupType[], updateData: ShopMapCamera[]) {
+  for (let i = 0; i < data.length; i++) {
+    const camera = data[i];
+    const matchedCamera = updateData.find((item) => item.code === camera.id);
+    if (matchedCamera) {
+      camera.groupConfig.visible = matchedCamera.integrationState === 0 ? true : false;
     }
   }
-
-  const bgConfig = ref({
-    x: 0,
-    y: 0,
-    id: 'bgImg',
-    width: bgImg.width,
-    height: bgImg.height,
-    image: bgImg,
-    name: 'bgImg',
-    backgroundSize: 'cover',
-  });
-
-  //待修改
-  const transformerConfig = ref({
-    keepRatio: true,
-    rotateAnchorOffset: 30,
-    enabledAnchors: ['top-left', 'top-right', 'bottom-left', 'bottom-right'],
-  });
-
-  //背景大小变换
-  const resizeContainer = (width, height) => {
-    stageConfig.value.width = width;
-    stageConfig.value.height = height;
-    layer.value.getNode().draw();
+}
+
+const bgConfig = ref({
+  x: 0,
+  y: 0,
+  id: 'bgImg',
+  width: bgImg.width,
+  height: bgImg.height,
+  image: bgImg,
+  name: 'bgImg',
+  backgroundSize: 'cover',
+});
+
+//待修改
+const transformerConfig = ref({
+  keepRatio: true,
+  rotateAnchorOffset: 30,
+  enabledAnchors: ['top-left', 'top-right', 'bottom-left', 'bottom-right'],
+});
+
+//背景大小变换
+const resizeContainer = (width, height) => {
+  stageConfig.value.width = width;
+  stageConfig.value.height = height;
+  layer.value.getNode().draw();
+};
+
+const defaultIconConfig = ref({
+  x: 18,
+  y: -16,
+  width: 16,
+  height: 16,
+  id: 'defaultIcon',
+  image: favoritesImg,
+});
+
+//取消
+const handleMouseOver = (e) => {
+  document.oncontextmenu = () => {
+    return false;
   };
 
-  const defaultIconConfig = ref({
-    x: 18,
-    y: -16,
-    width: 16,
-    height: 16,
-    id: 'defaultIcon',
-    image: favoritesImg,
-  });
-
-  //取消
-  const handleMouseOver = (e) => {
-    document.oncontextmenu = () => {
-      return false;
-    };
-
-    const group = e.target.parent;
-    if (group.id() === defaultCameraId.value) {
-      tipShow.value = true;
-      const stage = transformer.value.getNode().getStage();
-      const defaultNode = stage.findOne('#defaultIcon');
-      const tipPosition = defaultNode.absolutePosition();
-      posTipX.value = Number(tipPosition?.x.toFixed(2)) || 0;
-      posTipY.value = Number(tipPosition?.y.toFixed(2)) || 0;
-      const angle = group.rotation() >= 0 ? group.rotation() : group.rotation() + 360;
-      if (angle >= 30) {
-        if (angle <= 150) {
-          pos.value = TipPositionEnum.RIGHT;
-          posTipX.value += 26;
-          posTipY.value -= 17;
-        } else if (angle <= 210) {
-          pos.value = TipPositionEnum.BOTTOM;
-          posTipY.value += 26;
-          posTipX.value -= 50;
-        } else {
-          pos.value = TipPositionEnum.LEFT;
-          posTipX.value -= 121; 
-          posTipY.value -= 25;
-        }
+  const group = e.target.parent;
+  if (group.id() === defaultCameraId.value) {
+    tipShow.value = true;
+    const stage = transformer.value.getNode().getStage();
+    const defaultNode = stage.findOne('#defaultIcon');
+    const tipPosition = defaultNode.absolutePosition();
+    posTipX.value = Number(tipPosition?.x.toFixed(2)) || 0;
+    posTipY.value = Number(tipPosition?.y.toFixed(2)) || 0;
+    const angle = group.rotation() >= 0 ? group.rotation() : group.rotation() + 360;
+    if (angle >= 30) {
+      if (angle <= 150) {
+        pos.value = TipPositionEnum.RIGHT;
+        posTipX.value += 26;
+        posTipY.value -= 17;
+      } else if (angle <= 210) {
+        pos.value = TipPositionEnum.BOTTOM;
+        posTipY.value += 26;
+        posTipX.value -= 50;
       } else {
-        posTipY.value -= 61;
-        posTipX.value -= 43;
+        pos.value = TipPositionEnum.LEFT;
+        posTipX.value -= 121;
+        posTipY.value -= 25;
       }
+    } else {
+      posTipY.value -= 61;
+      posTipX.value -= 43;
     }
+  }
+};
+// 还原浏览器默认鼠标事件
+const handleMouseLeave = () => {
+  document.oncontextmenu = () => {
+    return true;
   };
-  // 还原浏览器默认鼠标事件
-  const handleMouseLeave = () => {
-    document.oncontextmenu = () => {
-      return true;
-    };
-    tipShow.value = false;
+  tipShow.value = false;
+};
+
+const handleDragStart = () => {
+  tipShow.value = false;
+  emit('change', true);
+};
+
+const handleStageClick = (e: any) => {
+  defaultShow.value = false;
+  disabledSet.value = false;
+  document.oncontextmenu = () => {
+    return false;
   };
-
-  const handleDragStart = () => {
-    tipShow.value = false;
+  const parent = e.target.parent;
+  //判断是否点击相机组
+  if (parent.hasName('group')) {
+    lastClickedGroupId.value = parent.id();
     emit('change', true);
-  };
-
-  const handleStageClick = (e: any) => {
-    defaultShow.value = false;
-    disabledSet.value = false;
-    document.oncontextmenu = () => {
-      return false;
-    };
-    const parent = e.target.parent;
-    //判断是否点击相机组
-    if (parent.hasName('group')) {
+    // 判断是否为右键点击
+    if (e.evt.button === 2) {
       lastClickedGroupId.value = parent.id();
-      emit('change', true);
-      // 判断是否为右键点击
-      if (e.evt.button === 2) {
-        lastClickedGroupId.value = parent.id();
-        const clickedVideoUrl = props.filterData.find(
-          (item) => item.code === lastClickedGroupId.value,
-        );
-        if (clickedVideoUrl) {
-          lastClickedVideoUrl.value = clickedVideoUrl.pushstreamIp;
-        }
-        isShow.value = false;
-        posX.value = e.evt.offsetX + 20;
-        posY.value = e.evt.offsetY;
-        disabledSet.value = defaultCameraId.value === parent.id();
-        defaultShow.value = true;
+      const clickedVideoUrl = props.filterData.find(
+        (item) => item.code === lastClickedGroupId.value,
+      );
+      if (clickedVideoUrl) {
+        lastClickedVideoUrl.value = clickedVideoUrl.pushStreamDTO.videoUrls;
       }
-    } else {
-      lastClickedGroupId.value = null;
-      //取消transformer选择
-      const transformerNode = transformer.value.getNode();
-      transformerNode.nodes([]);
-    }
-  };
-
-  const handleCameraClick = (camera) => {
-    tipShow.value = false;
-    const transformerNode = transformer.value.getNode();
-    const stage = transformerNode.getStage();
-    const cameraNode = stage.findOne('#' + camera.id);
-    // 将变换器附加到点击的相机
-    transformerNode.nodes([cameraNode]);
-    transformerNode.moveToTop();
-    emit('change', true);
-  };
-
-  //添加相机
-  const addCamera = (id: string) => {
-    const existingCamera = cameras.value.find((camera) => camera.id === id);
-    if (existingCamera) return;
-    const config = {
-      width: cameraIconSize.width,
-      height: cameraIconSize.height,
-      image: camImg,
-      name: 'image',
-      id: id,
-    };
-    const groupConfig = {
-      x: 50,
-      y: 50,
-      draggable: true,
-      name: 'group',
-    };
-    const cameraDetail = {
-      id,
-      groupConfig,
-      config,
-      isDefault: false,
-    };
-    cameras.value.push(cameraDetail);
-    //当只有一个相机时,自动设置默认相机
-    if (cameras.value.length === 1) {
-      cameras.value[0].isDefault = true;
-      defaultCameraId.value = id;
+      isShow.value = false;
+      posX.value = e.evt.offsetX + 20;
+      posY.value = e.evt.offsetY;
+      disabledSet.value = defaultCameraId.value === parent.id();
+      defaultShow.value = true;
     }
-    emit('change', true);
-  };
-
-  //设置默认相机
-  const setDefaultCamera = () => {
-    //选中的相机id号
-    defaultCameraId.value = lastClickedGroupId.value!;
-    cameras.value.forEach((item) => {
-      if (item.id === defaultCameraId.value) {
-        item.isDefault = true;
-      } else {
-        item.isDefault = false;
-      }
-    });
+  } else {
+    lastClickedGroupId.value = null;
+    //取消transformer选择
     const transformerNode = transformer.value.getNode();
-    const stage = transformerNode.getStage();
     transformerNode.nodes([]);
-    const cameraNode = stage.findOne('#' + defaultCameraId.value);
-    // 将变换器附加到点击的相机
-    transformerNode.nodes([cameraNode]);
-    defaultShow.value = false;
-    emit('change', true);
-  };
-
-  const isShow = ref<boolean>(false);
-
-  const previewCamera = () => {
-    videoUrl.value = lastClickedVideoUrl.value!;
-    isShow.value = true;
-    defaultShow.value = false;
-  };
-
-  const closePreview = () => {
-    isShow.value = false;
-    defaultShow.value = false;
-  };
-
-  watch(
-    lastClickedGroupId,
-    () => {
-      emit('changeDefaultCamera', lastClickedGroupId.value);
-    },
-    { immediate: true },
-  );
-
-  watch(
-    () => cameras.value,
-    () => {
-      emit('sendCameraId', cameras.value);
-    },
-    { immediate: true, deep: true },
-  );
-
-  watch(
-    () => props.filterData,
-    () => {
-      addIntegrationState(cameras.value, props.filterData);
-    },
-    { immediate: true, deep: true },
-  );
-
-  const clearBg = () => {
-    bgImg.src = null as any as string;
-    layer.value.getNode().draw();
+  }
+};
+
+const handleCameraClick = (camera) => {
+  tipShow.value = false;
+  const transformerNode = transformer.value.getNode();
+  const stage = transformerNode.getStage();
+  const cameraNode = stage.findOne('#' + camera.id);
+  // 将变换器附加到点击的相机
+  transformerNode.nodes([cameraNode]);
+  transformerNode.moveToTop();
+  emit('change', true);
+};
+
+//添加相机
+const addCamera = (id: string) => {
+  const existingCamera = cameras.value.find((camera) => camera.id === id);
+  if (existingCamera) return;
+  const config = {
+    width: cameraIconSize.width,
+    height: cameraIconSize.height,
+    image: camImg,
+    name: 'image',
+    id: id,
   };
-
-  //添加背景
-  const addBg = (imgBg) => {
-    return new Promise((resolve) => {
-      bgImgUrl.value = imgBg;
-      bgImg.src = imgBg;
-      bgImg.onload = () => {
-        bgConfig.value.width = bgImg.width;
-        bgConfig.value.height = bgImg.height;
-        resizeContainer(bgImg.width, bgImg.height);
-        resolve(null);
-      };
-    });
+  const groupConfig = {
+    x: 50,
+    y: 50,
+    draggable: true,
+    name: 'group',
   };
-
-  //根据id找到对应的相机
-  const findCamera = (cameraId: string) => {
-    const camera = cameras.value.find((item) => item.id === cameraId);
-    return camera;
+  const cameraDetail = {
+    id,
+    groupConfig,
+    config,
+    isDefault: false,
   };
-
-  //保存布局
-  const saveLayout = () => {
-    const stage = transformer.value.getNode().getStage();
-    const groups = stage.find('.group');
-    const tempList = cloneDeep(cameras.value);
-    const camerasLists = tempList.map((item, index) => {
-      item.groupConfig.x = groups[index].attrs.x;
-      item.groupConfig.y = groups[index].attrs.y;
-      item.groupConfig.rotation = groups[index].attrs.rotation || 0;
-      item.groupConfig.scaleX = groups[index].attrs.scaleX || 1;
-      item.groupConfig.scaleY = groups[index].attrs.scaleY || 1;
-      // item.config.url = cameraImgSrc;
-      return item;
-    });
-    const layout = {
-      stageConfig: stageConfig.value,
-      bgConfig: bgConfig.value,
-      bgImgUrl: bgImgUrl.value,
-      defaultCameraId: defaultCameraId.value,
-      cameraList: camerasLists,
+  cameras.value.push(cameraDetail);
+  //当只有一个相机时,自动设置默认相机
+  if (cameras.value.length === 1) {
+    cameras.value[0].isDefault = true;
+    defaultCameraId.value = id;
+  }
+  emit('change', true);
+};
+
+//设置默认相机
+const setDefaultCamera = () => {
+  //选中的相机id号
+  defaultCameraId.value = lastClickedGroupId.value!;
+  cameras.value.forEach((item) => {
+    if (item.id === defaultCameraId.value) {
+      item.isDefault = true;
+    } else {
+      item.isDefault = false;
+    }
+  });
+  const transformerNode = transformer.value.getNode();
+  const stage = transformerNode.getStage();
+  transformerNode.nodes([]);
+  const cameraNode = stage.findOne('#' + defaultCameraId.value);
+  // 将变换器附加到点击的相机
+  transformerNode.nodes([cameraNode]);
+  defaultShow.value = false;
+  emit('change', true);
+};
+
+const isShow = ref<boolean>(false);
+
+const previewCamera = () => {
+  videoUrl.value = lastClickedVideoUrl.value!;
+  if (!videoUrl.value) {
+    ElMessage.warning('视频流不存在!');
+    return
+  }
+  isShow.value = true;
+  defaultShow.value = false;
+};
+
+const closePreview = () => {
+  isShow.value = false;
+  defaultShow.value = false;
+};
+
+watch(
+  lastClickedGroupId,
+  () => {
+    emit('changeDefaultCamera', lastClickedGroupId.value);
+  },
+  { immediate: true },
+);
+
+watch(
+  () => cameras.value,
+  () => {
+    emit('sendCameraId', cameras.value);
+  },
+  { immediate: true, deep: true },
+);
+
+watch(
+  () => props.filterData,
+  () => {
+    addIntegrationState(cameras.value, props.filterData);
+  },
+  { immediate: true, deep: true },
+);
+
+const clearBg = () => {
+  bgImg.src = null as any as string;
+  layer.value.getNode().draw();
+};
+
+//添加背景
+const addBg = (imgBg) => {
+  return new Promise((resolve) => {
+    bgImgUrl.value = imgBg;
+    bgImg.src = imgBg;
+    bgImg.onload = () => {
+      bgConfig.value.width = bgImg.width;
+      bgConfig.value.height = bgImg.height;
+      resizeContainer(bgImg.width, bgImg.height);
+      resolve(null);
     };
-    emit('change', false);
-    return JSON.stringify(layout);
-  };
-
-  //删除相机
-  // const handleKeyDown = (e) => {
-  //   if (e.keyCode === 46 || e.code === 'Delete' || e.keyCode === 8 || e.code === 'Backspace') {
-  //     if (lastClickedGroupId.value === defaultCameraId.value) {
-  //       // ElMessage.error({
-  //       //   message: '无法删除默认相机',
-  //       // });
-  //       // return;
-  //       ElMessageBox.confirm('此相机为默认相机,您确认要删除此相机?', 'Warning', {
-  //         confirmButtonText: '确认',
-  //         cancelButtonText: '取消',
-  //         type: 'warning',
-  //       })
-  //         .then(() => {
-  //           const index = cameras.value.findIndex((item) => item.id === lastClickedGroupId.value);
-  //           index >= 0 && cameras.value.splice(index, 1);
-  //           lastClickedGroupId.value = '';
-
-  //           //取消transformer
-  //           const transformerNode = transformer.value.getNode();
-  //           transformerNode.nodes([]);
-  //         })
-  //         .catch(() => {});
-  //     } else {
-  //       const index = cameras.value.findIndex((item) => item.id === lastClickedGroupId.value);
-  //       index >= 0 && cameras.value.splice(index, 1);
-  //       lastClickedGroupId.value = '';
-
-  //       //取消transformer
-  //       const transformerNode = transformer.value.getNode();
-  //       transformerNode.nodes([]);
-  //     }
-  //     emit('change', true);
-  //   }
-  // };
-
-  //重置
-  const resetMap = () => {
-    bgImgUrl.value = null;
-    clearBg();
-    cameras.value = [];
-    lastClickedGroupId.value = null;
-    lastClickedGroupId.value = null;
-    videoUrl.value = '';
-    isShow.value = false;
-    defaultCameraId.value = '';
+  });
+};
+
+//根据id找到对应的相机
+const findCamera = (cameraId: string) => {
+  const camera = cameras.value.find((item) => item.id === cameraId);
+  return camera;
+};
+
+//保存布局
+const saveLayout = () => {
+  const stage = transformer.value.getNode().getStage();
+  const groups = stage.find('.group');
+  const tempList = cloneDeep(cameras.value);
+  const camerasLists = tempList.map((item, index) => {
+    item.groupConfig.x = groups[index].attrs.x;
+    item.groupConfig.y = groups[index].attrs.y;
+    item.groupConfig.rotation = groups[index].attrs.rotation || 0;
+    item.groupConfig.scaleX = groups[index].attrs.scaleX || 1;
+    item.groupConfig.scaleY = groups[index].attrs.scaleY || 1;
+    // item.config.url = cameraImgSrc;
+    return item;
+  });
+  const layout = {
+    stageConfig: stageConfig.value,
+    bgConfig: bgConfig.value,
+    bgImgUrl: bgImgUrl.value,
+    defaultCameraId: defaultCameraId.value,
+    cameraList: camerasLists,
   };
-
-  /** 导入布局json */
-  const createMap = (layout, selectId) => {
-    addBg(layout.bgImgUrl).then((_res) => {
-      const unExitList = [] as any[];
-      stageConfig.value = layout.stageConfig;
-      defaultCameraId.value = layout.defaultCameraId;
-      layout.cameraList = layout.cameraList
-        ?.map((item) => {
-          item.config.image = camImg;
-          const width = item.config.width;
-          const height = item.config.height;
-          const ratio = width / height;
-          /** 老版本的相机icon比例在1.4左右,新icon已经更新,比例是1:1,为了避免新icon渲染出来变形,需要调整icon大小
-           * 如果图标的比例在该范围,那么会将它设置为新图标的icon大小 */
-          if (1.3 < ratio && ratio < 1.5) {
-            item.config.width = cameraIconSize.width;
-            item.config.height = cameraIconSize.height;
-          }
-
-          return item;
+  emit('change', false);
+  return JSON.stringify(layout);
+};
+
+//删除相机
+const handleKeyDown = (e) => {
+  console.log(e)
+  if (e.keyCode === 46 || e.code === 'Delete' || e.keyCode === 8 || e.code === 'Backspace') {
+    if (lastClickedGroupId.value === defaultCameraId.value) {
+      // ElMessage.error({
+      //   message: '无法删除默认相机',
+      // });
+      // return;
+      ElMessageBox.confirm('此相机为默认相机,您确认要删除此相机?', 'Warning', {
+        confirmButtonText: '确认',
+        cancelButtonText: '取消',
+        type: 'warning',
+      })
+        .then(() => {
+          const index = cameras.value.findIndex((item) => item.id === lastClickedGroupId.value);
+          index >= 0 && cameras.value.splice(index, 1);
+          lastClickedGroupId.value = '';
+
+          //取消transformer
+          const transformerNode = transformer.value.getNode();
+          transformerNode.nodes([]);
         })
-        ?.filter((cam) => {
-          if (shopCameraList.value.findIndex((x) => x.code === cam.id) >= 0) {
-            return true;
-          } else {
-            unExitList.push(cam.code);
-            return false;
-          }
-        });
-      if (unExitList.length > 0) {
-        ElMessage.warning('部分相机不存在,已为您删除!');
-
-        const layoutNew = cloneDeep(layout);
-        updateMinMapViewLayoutApi({
-          layout: JSON.stringify({ ...layoutNew, isUploadBg: true }),
-          targetId: String(selectId),
-          viewType: 2
-        });
-      }
+        .catch(() => { });
+    } else {
+      const index = cameras.value.findIndex((item) => item.id === lastClickedGroupId.value);
+      index >= 0 && cameras.value.splice(index, 1);
+      lastClickedGroupId.value = '';
 
-      cameras.value = layout.cameraList;
-    });
-  };
+      //取消transformer
+      const transformerNode = transformer.value.getNode();
+      transformerNode.nodes([]);
+    }
+    emit('change', true);
+  }
+};
+
+//重置
+const resetMap = () => {
+  bgImgUrl.value = null;
+  clearBg();
+  cameras.value = [];
+  lastClickedGroupId.value = null;
+  lastClickedGroupId.value = null;
+  videoUrl.value = '';
+  isShow.value = false;
+  defaultCameraId.value = '';
+};
+
+/** 导入布局json */
+const createMap = (layout, selectId) => {
+  addBg(layout.bgImgUrl).then((_res) => {
+    const unExitList = [] as any[];
+    stageConfig.value = layout.stageConfig;
+    defaultCameraId.value = layout.defaultCameraId;
+    layout.cameraList = layout.cameraList
+      ?.map((item) => {
+        item.config.image = camImg;
+        const width = item.config.width;
+        const height = item.config.height;
+        const ratio = width / height;
+        /** 老版本的相机icon比例在1.4左右,新icon已经更新,比例是1:1,为了避免新icon渲染出来变形,需要调整icon大小
+         * 如果图标的比例在该范围,那么会将它设置为新图标的icon大小 */
+        if (1.3 < ratio && ratio < 1.5) {
+          item.config.width = cameraIconSize.width;
+          item.config.height = cameraIconSize.height;
+        }
 
-  defineExpose({
-    addBg,
-    createMap,
-    handleCameraClick,
-    findCamera,
-    resetMap,
-    addCamera,
-    resizeContainer,
-    saveLayout,
-  });
+        return item;
+      })
+      ?.filter((cam) => {
+        if (shopCameraList.value.findIndex((x) => x.code === cam.id) >= 0) {
+          return true;
+        } else {
+          unExitList.push(cam.code);
+          return false;
+        }
+      });
+    if (unExitList.length > 0) {
+      ElMessage.warning('部分相机不存在,已为您删除!');
+
+      const layoutNew = cloneDeep(layout);
+      updateMinMapViewLayoutApi({
+        layout: JSON.stringify({ ...layoutNew, isUploadBg: true }),
+        targetId: String(selectId),
+        viewType: 2
+      });
+    }
 
-  onMounted(() => {
-    // window.addEventListener('keydown', handleKeyDown);
-    camImg.src = cameraImgSrc;
+    cameras.value = layout.cameraList;
   });
-
-  // onBeforeUnmount(() => {
-  //   window.removeEventListener('keydown', handleKeyDown);
-  // });
+};
+
+defineExpose({
+  addBg,
+  createMap,
+  handleCameraClick,
+  findCamera,
+  resetMap,
+  addCamera,
+  resizeContainer,
+  saveLayout,
+});
+onMounted(() => {
+  camImg.src = cameraImgSrc;
+  // stageAll.value.addEventListener('keydown', handleKeyDown);
+});
+
+// onBeforeUnmount(() => {
+//   stageAll.value.removeEventListener('keydown', handleKeyDown);
+// });
 </script>
 
 <style scoped lang="scss">
-  .opt-container {
-    width: 160px;
-    padding: 10px;
-    border-radius: 5px;
-    background-color: #ffffff;
-    box-shadow: 5px 5px 5px #a3a5a5;
-  }
-  .opt-item {
-    height: 30px;
-    font-size: 14px;
-    color: #404040;
-    display: flex;
-    justify-content: flex-start;
-    align-items: center;
-    padding-left: 8px;
-    border-radius: 3px;
-    cursor: pointer;
-
-    &:hover {
-      background-color: #f1f2f5;
-    }
-  }
-
-  .disabled {
+.opt-container {
+  width: 160px;
+  padding: 10px;
+  border-radius: 5px;
+  background-color: #ffffff;
+  box-shadow: 5px 5px 5px #a3a5a5;
+}
+
+.opt-item {
+  height: 30px;
+  font-size: 14px;
+  color: #404040;
+  display: flex;
+  justify-content: flex-start;
+  align-items: center;
+  padding-left: 8px;
+  border-radius: 3px;
+  cursor: pointer;
+
+  &:hover {
     background-color: #f1f2f5;
-    color: #bcbdc0;
-    cursor: not-allowed;
   }
+}
+
+.disabled {
+  background-color: #f1f2f5;
+  color: #bcbdc0;
+  cursor: not-allowed;
+}
 </style>

+ 22 - 21
src/views/map-config/mini-map/MiniMapConfig.vue

@@ -68,8 +68,8 @@
       </div>
       <div ref="drawContainer" v-show="isUploadBg || shopCameraList.length !== 1" class="draw-container"
         :class="{ 'bg-background': hasBg ? true : false }">
-        <KonvaMap ref="konvaMap" :filter-data="filterShopCameraList" @change-default-camera="changeDefault"
-          @send-camera-id="sendCameras" @change="changeMap" v-moveable:1 />
+        <KonvaMap ref="konvaMap" :filter-data="filterShopCameraList" :camera-list="shopCameraList"
+          @change-default-camera="changeDefault" @send-camera-id="sendCameras" @change="changeMap" v-moveable:1 />
         <div id="editContainer" v-moveable:1></div>
         <el-upload v-if="!hasBg" class="upload-icon flex justify-center items-center" :action="actionUrl"
           :show-file-list="false" :before-upload="handleBeforeUpload" :on-success="handleAvatarSuccess"
@@ -100,7 +100,7 @@ import rollback from '@/assets/rollback.png'
 import router from '@/router';
 import { ShopMapCamera } from '@/types/scene/type'
 const cameraStatus = useCameraStatus();
-const { openInterval, closeInterval } = cameraStatus;
+const { openIntervalNew, closeInterval } = cameraStatus;
 
 // const miniMap = useMiniMap();
 // const { scenesTree, selectedShopCode, selectedShopDetail } = storeToRefs(miniMap);
@@ -196,7 +196,7 @@ const getMapLayout = async (id: number) => {
 }
 const getShopContent = async (id: number) => {
   await getShowCameras(id);
-  // const idList = filterShopCameraList.value.map((item) => item.id);
+  const idList = filterShopCameraList.value.map((item) => item.id);
   const res = await getMapLayout(id);
   if (!res) return;
   hasBg.value = true;
@@ -208,23 +208,24 @@ const getShopContent = async (id: number) => {
     hasBg.value = false;
     isUploadBg.value = res.isUploadBg;
   }
-  // openInterval(codeList, (targetData) => {
-  //   updataState(filterShopCameraList.value, targetData);
-  // });
-  // getMapLayout(code).then((res) => {
-  //   if (!res) {
-  //     return;
-  //   }
-  //   hasBg.value = true;
-  //   isMap.value = res.isUploadBg;
-  //   if (res.isUploadBg) {
-  //     isUploadBg.value = true;
-  //     konvaMap.value.createMap(res, selectedShopDetail.value?.id);
-  //   } else {
-  //     hasBg.value = false;
-  //     isUploadBg.value = res.isUploadBg;
-  //   }
-  // });
+  openIntervalNew(idList, (targetData) => {
+    updataState(filterShopCameraList.value, targetData);
+  });
+  getMapLayout(id).then((res) => {
+    if (!res) {
+      return;
+    }
+    hasBg.value = true;
+    isMap.value = res.isUploadBg;
+    if (res.isUploadBg) {
+      isUploadBg.value = true;
+      console.log(res)
+      konvaMap.value.createMap(res, selectedShopId.value);
+    } else {
+      hasBg.value = false;
+      isUploadBg.value = res.isUploadBg;
+    }
+  });
 };
 const selectedShopId = ref();
 const selectedName = ref();

+ 41 - 41
src/views/map-config/mini-map/use-mini-map.ts

@@ -19,58 +19,58 @@ export const useMiniMap = defineStore('mini-map', () => {
   const { scenesTree, flattenedWorkshops } = toRefs(sceneInfos);
   const { getScenesTree } = sceneInfos;
 
-  const selectedShopCode = ref<string>();
-  const selectedShopDetail = computed(() =>
-    flattenedWorkshops.value.find((space) => space.code === selectedShopCode.value),
-  );
+  // const selectedShopCode = ref<string>();
+  // const selectedShopDetail = computed(() =>
+  //   flattenedWorkshops.value.find((space) => space.code === selectedShopCode.value),
+  // );
 
-  const getShopDetailByCode = (code: string) => {
-    return flattenedWorkshops.value.find((space) => space.code === code);
-  };
+  // const getShopDetailByCode = (code: string) => {
+  //   return flattenedWorkshops.value.find((space) => space.code === code);
+  // };
   const bgImgUrl = ref('');
 
-  const shopCameraList = ref<ShopMapCamera[]>([]);
+  // const shopCameraList = ref<ShopMapCamera[]>([]);
 
-  const getShowCameras = (code: string) => {
-    shopCameraList.value = [];
-    const id = getShopDetailByCode(code)?.id;
+  // const getShowCameras = (code: string) => {
+  //   shopCameraList.value = [];
+  //   const id = getShopDetailByCode(code)?.id;
 
-    if (!id) {
-      ElMessage.error('摄像头code未找到对应信息');
-      return Promise.resolve();
-    }
-    return getCamerasByWorkShopId({ workshopId: id }).then((res) => {
-      res.children?.forEach((space) => {
-        if (space.children && space.children.length > 0) {
-          space.children.forEach((camera) => {
-            shopCameraList.value.push({ ...camera, isSet: 0, workSpaceName: space.name });
-          });
-        }
-      });
-    });
-  };
+  //   if (!id) {
+  //     ElMessage.error('摄像头code未找到对应信息');
+  //     return Promise.resolve();
+  //   }
+  //   return getCamerasByWorkShopId({ workshopId: id }).then((res) => {
+  //     res.children?.forEach((space) => {
+  //       if (space.children && space.children.length > 0) {
+  //         space.children.forEach((camera) => {
+  //           shopCameraList.value.push({ ...camera, isSet: 0, workSpaceName: space.name });
+  //         });
+  //       }
+  //     });
+  //   });
+  // };
 
-  const getMapLayout = (code: string) => {
-    const shopId = getShopDetailByCode(code)?.id || '';
-    if (!shopId) {
-      ElMessage.error('摄像头code未找到对应信息');
-      return Promise.reject();
-    }
-    return getWorkshopMiniMapLayoutApi(shopId).then((res) => {
-      const layoutJSON = res?.layout ? safeParse(res.layout) : null;
-      return layoutJSON;
-    });
-  };
+  // const getMapLayout = (code: string) => {
+  //   const shopId = getShopDetailByCode(code)?.id || '';
+  //   if (!shopId) {
+  //     ElMessage.error('摄像头code未找到对应信息');
+  //     return Promise.reject();
+  //   }
+  //   return getWorkshopMiniMapLayoutApi(shopId).then((res) => {
+  //     const layoutJSON = res?.layout ? safeParse(res.layout) : null;
+  //     return layoutJSON;
+  //   });
+  // };
 
   return {
-    selectedShopDetail,
+    // selectedShopDetail,
     bgImgUrl,
     scenesTree,
-    shopCameraList,
+    // shopCameraList,
     getScenesTree,
-    getShowCameras,
-    getMapLayout,
-    selectedShopCode,
+    // getShowCameras,
+    // getMapLayout,
+    // selectedShopCode,
   };
 });
 

+ 4 - 223
src/views/page-config/PageCameraList.vue

@@ -1,229 +1,10 @@
 <template>
-  <div class="scene-layout">
-    <header class="scene-layout__header">
-      <div class="header__btn" @click="router.back">
-        <img :src="rollback" />
-        <span>返回</span>
-      </div>
-    </header>
-    <main class="scene-layout__main">
-      <section v-if="length > 0" class="main__layout">
-        <el-card
-          v-for="company in companyList"
-          :key="company.id"
-          shadow="hover"
-          class="layout-cards"
-        >
-          <div class="layout-card" @click="handleClickCompany(company.id, company.name)">
-            <div v-if="company.layout">
-              <MapContainerSmall
-                ref="mapContainerRef"
-                :bg-image-url="(company.layout as any).bgInfo.img"
-                :show-shops="(company.layout as any).shopList"
-                class="content-pic"
-              />
-            </div>
-
-            <div v-else>
-              <img :src="noLayout" />
-              <span>请添加场景布局</span>
-            </div>
-          </div>
-          <template #footer>
-            <span class="footer--default">{{ company.name }}</span>
-            <div class="icons">
-              <el-image
-                v-if="company.layout"
-                :src="preview"
-                :preview-src-list="[company.layout]"
-                hide-on-click-modal
-                fit="cover"
-              />
-
-              <img :src="edit" @click="handleClickCompany(company.id, company.name)" />
-            </div>
-          </template>
-        </el-card>
-      </section>
-      <section class="main__empty" v-else>
-        <img :src="empty" />
-        <span>
-          目前无内容,
-          <router-link to="/scene/workshop">请先添加公司</router-link>
-        </span>
-      </section>
-    </main>
-  </div>
+  <BasicLayoutList :layout-type="LayoutConfigType.camera" />
 </template>
 
 <script lang="ts" setup>
-  import rollback from '@/assets/rollback.png';
-  import empty from '@/assets/images/table/table-empty.png';
-  import noLayout from '@/assets/images/page-config/no-layout.png';
-  import preview from '@/assets/images/table/table-preview.png';
-  import edit from '@/assets/images/table/table-edit.png';
-  import router from '@/router';
-  import { CompanyInfoList } from '@/types/scene-layout/type';
-  import { ViewType } from '@/types/page-config/type';
-  import {
-    getCompanyListApi,
-    getPcCompanyLayoutListApi,
-    getMobileCompanyLayoutList,
-  } from '@/api/scene/scene';
-  import { computed, onMounted, ref } from 'vue';
-  import MapContainerSmall from '@/views/page-config/component/mapContainer/MapContainerSmall.vue';
-  const companyList = ref<CompanyInfoList[]>([]);
-  const length = computed(() => {
-    return companyList.value.length;
-  });
-
-  const viewType = ref<number>();
-
-  const handleClickCompany = (companyId, companyName) => {
-    router.push({
-      path: '/layout/scene-config',
-      query: { companyId, companyName, viewType: viewType.value },
-    });
-  };
-
-  onMounted(async () => {
-    viewType.value = Number(router.currentRoute.value.query.viewType);
-    companyList.value = await getCompanyListApi();
-    const companyIDList = companyList.value.map((x) => x.id);
-
-    const platformApiMap = {
-      [ViewType.companyHomepage_PC]: getPcCompanyLayoutListApi,
-      [ViewType.companyHomepage_phone]: getMobileCompanyLayoutList,
-    };
-
-    platformApiMap[viewType.value]({ companyIds: companyIDList }).then((res) => {
-      res.map((companyWithLayout) => {
-        companyList.value.find((company) => company.id === companyWithLayout.id)!.layout =
-          JSON.parse(companyWithLayout.layout);
-      });
-    });
-  });
+import BasicLayoutList from './component/BasicLayoutList.vue';
+import { LayoutConfigType } from '@/types/page-config/type';
 </script>
 
-<style lang="scss" scoped>
-  .footer--default {
-    opacity: 0.45;
-  }
-
-  .scene-layout {
-    position: absolute;
-    top: 0;
-    left: 0;
-    width: 100%;
-    height: calc(100vh - 64px);
-
-    &__header {
-      display: flex;
-      align-items: center;
-      width: inherit;
-      height: 54px;
-      padding-left: 23px;
-      background: #ffffff;
-      border-top: 1px solid rgba(0, 0, 0, 0.1);
-      box-shadow: 0px 0px 6px 0px rgba(0, 0, 0, 0.12);
-
-      .header__btn {
-        display: flex;
-        gap: 10px;
-        cursor: pointer;
-      }
-
-      img {
-        width: 14px;
-        height: 14px;
-      }
-
-      span {
-        line-height: 14px;
-
-        &:hover {
-          text-shadow: 5px 5px 6px rgba(0, 0, 0, 0.12);
-        }
-      }
-    }
-
-    &__main {
-      margin-top: 12px;
-      width: inherit;
-      height: calc(100vh - 64px - 54px - 12px);
-      background: #ffffff;
-      box-shadow: 0px 0px 6px 0px rgba(0, 0, 0, 0.12);
-      border-radius: 6px;
-    }
-
-    .main__layout {
-      display: flex;
-      gap: 25px;
-      flex-wrap: wrap;
-      align-content: flex-start;
-      width: inherit;
-      height: inherit;
-      padding: 22px 24px 22px 24px;
-    }
-
-    .layout-cards {
-      width: 216px;
-      height: 190px;
-
-      .layout-card div {
-        display: flex;
-        flex-direction: column;
-        justify-content: center;
-        align-items: center;
-        gap: 4px;
-      }
-    }
-
-    :deep(.el-card__body) {
-      width: inherit;
-      height: 145px;
-      display: flex;
-      flex-direction: column;
-      justify-content: center;
-      align-items: center;
-      padding: 0;
-      background: #f5f5f5;
-      cursor: pointer;
-    }
-
-    :deep(.el-card__footer) {
-      display: flex;
-      justify-content: space-between;
-      align-items: center;
-      width: inherit;
-      height: 45px;
-      padding: 0 12px 0 12px;
-
-      .icons {
-        display: flex;
-        gap: 10px;
-
-        img,
-        el-image {
-          cursor: pointer;
-          opacity: 0.4;
-          transition: opacity 0.5s ease-in-out;
-
-          &:hover {
-            opacity: 1;
-          }
-        }
-      }
-    }
-
-    .main__empty {
-      display: flex;
-      flex-direction: column;
-      gap: 2px;
-      justify-content: center;
-      align-items: center;
-      width: inherit;
-      height: inherit;
-    }
-  }
-</style>
+<style lang="scss" scoped></style>

+ 4 - 223
src/views/page-config/PageSceneList.vue

@@ -1,229 +1,10 @@
 <template>
-  <div class="scene-layout">
-    <header class="scene-layout__header">
-      <div class="header__btn" @click="router.back">
-        <img :src="rollback" />
-        <span>返回</span>
-      </div>
-    </header>
-    <main class="scene-layout__main">
-      <section v-if="length > 0" class="main__layout">
-        <el-card
-          v-for="company in companyList"
-          :key="company.id"
-          shadow="hover"
-          class="layout-cards"
-        >
-          <div class="layout-card" @click="handleClickCompany(company.id, company.name)">
-            <div v-if="company.layout">
-              <MapContainerSmall
-                ref="mapContainerRef"
-                :bg-image-url="(company.layout as any).bgInfo.img"
-                :show-shops="(company.layout as any).shopList"
-                class="content-pic"
-              />
-            </div>
-
-            <div v-else>
-              <img :src="noLayout" />
-              <span>请添加场景布局</span>
-            </div>
-          </div>
-          <template #footer>
-            <span class="footer--default">{{ company.name }}</span>
-            <div class="icons">
-              <el-image
-                v-if="company.layout"
-                :src="preview"
-                :preview-src-list="[company.layout]"
-                hide-on-click-modal
-                fit="cover"
-              />
-
-              <img :src="edit" @click="handleClickCompany(company.id, company.name)" />
-            </div>
-          </template>
-        </el-card>
-      </section>
-      <section class="main__empty" v-else>
-        <img :src="empty" />
-        <span>
-          目前无内容,
-          <router-link to="/scene/workshop">请先添加公司</router-link>
-        </span>
-      </section>
-    </main>
-  </div>
+  <BasicLayoutList :layout-type="LayoutConfigType.scene" />
 </template>
 
 <script lang="ts" setup>
-  import rollback from '@/assets/rollback.png';
-  import empty from '@/assets/images/table/table-empty.png';
-  import noLayout from '@/assets/images/page-config/no-layout.png';
-  import preview from '@/assets/images/table/table-preview.png';
-  import edit from '@/assets/images/table/table-edit.png';
-  import router from '@/router';
-  import { CompanyInfoList } from '@/types/scene-layout/type';
-  import { ViewType } from '@/types/page-config/type';
-  import {
-    getCompanyListApi,
-    getPcCompanyLayoutListApi,
-    getMobileCompanyLayoutList,
-  } from '@/api/scene/scene';
-  import { computed, onMounted, ref } from 'vue';
-  import MapContainerSmall from '@/views/page-config/component/mapContainer/MapContainerSmall.vue';
-  const companyList = ref<CompanyInfoList[]>([]);
-  const length = computed(() => {
-    return companyList.value.length;
-  });
-
-  const viewType = ref<number>();
-
-  const handleClickCompany = (companyId, companyName) => {
-    router.push({
-      path: '/layout/scene-config',
-      query: { companyId, companyName, viewType: viewType.value },
-    });
-  };
-
-  onMounted(async () => {
-    viewType.value = Number(router.currentRoute.value.query.viewType);
-    companyList.value = await getCompanyListApi();
-    const companyIDList = companyList.value.map((x) => x.id);
-
-    const platformApiMap = {
-      [ViewType.companyHomepage_PC]: getPcCompanyLayoutListApi,
-      [ViewType.companyHomepage_phone]: getMobileCompanyLayoutList,
-    };
-
-    platformApiMap[viewType.value]({ companyIds: companyIDList }).then((res) => {
-      res.map((companyWithLayout) => {
-        companyList.value.find((company) => company.id === companyWithLayout.id)!.layout =
-          JSON.parse(companyWithLayout.layout);
-      });
-    });
-  });
+import BasicLayoutList from './component/BasicLayoutList.vue';
+import { LayoutConfigType } from '@/types/page-config/type';
 </script>
 
-<style lang="scss" scoped>
-  .footer--default {
-    opacity: 0.45;
-  }
-
-  .scene-layout {
-    position: absolute;
-    top: 0;
-    left: 0;
-    width: 100%;
-    height: calc(100vh - 64px);
-
-    &__header {
-      display: flex;
-      align-items: center;
-      width: inherit;
-      height: 54px;
-      padding-left: 23px;
-      background: #ffffff;
-      border-top: 1px solid rgba(0, 0, 0, 0.1);
-      box-shadow: 0px 0px 6px 0px rgba(0, 0, 0, 0.12);
-
-      .header__btn {
-        display: flex;
-        gap: 10px;
-        cursor: pointer;
-      }
-
-      img {
-        width: 14px;
-        height: 14px;
-      }
-
-      span {
-        line-height: 14px;
-
-        &:hover {
-          text-shadow: 5px 5px 6px rgba(0, 0, 0, 0.12);
-        }
-      }
-    }
-
-    &__main {
-      margin-top: 12px;
-      width: inherit;
-      height: calc(100vh - 64px - 54px - 12px);
-      background: #ffffff;
-      box-shadow: 0px 0px 6px 0px rgba(0, 0, 0, 0.12);
-      border-radius: 6px;
-    }
-
-    .main__layout {
-      display: flex;
-      gap: 25px;
-      flex-wrap: wrap;
-      align-content: flex-start;
-      width: inherit;
-      height: inherit;
-      padding: 22px 24px 22px 24px;
-    }
-
-    .layout-cards {
-      width: 216px;
-      height: 190px;
-
-      .layout-card div {
-        display: flex;
-        flex-direction: column;
-        justify-content: center;
-        align-items: center;
-        gap: 4px;
-      }
-    }
-
-    :deep(.el-card__body) {
-      width: inherit;
-      height: 145px;
-      display: flex;
-      flex-direction: column;
-      justify-content: center;
-      align-items: center;
-      padding: 0;
-      background: #f5f5f5;
-      cursor: pointer;
-    }
-
-    :deep(.el-card__footer) {
-      display: flex;
-      justify-content: space-between;
-      align-items: center;
-      width: inherit;
-      height: 45px;
-      padding: 0 12px 0 12px;
-
-      .icons {
-        display: flex;
-        gap: 10px;
-
-        img,
-        el-image {
-          cursor: pointer;
-          opacity: 0.4;
-          transition: opacity 0.5s ease-in-out;
-
-          &:hover {
-            opacity: 1;
-          }
-        }
-      }
-    }
-
-    .main__empty {
-      display: flex;
-      flex-direction: column;
-      gap: 2px;
-      justify-content: center;
-      align-items: center;
-      width: inherit;
-      height: inherit;
-    }
-  }
-</style>
+<style lang="scss" scoped></style>

+ 8 - 5
src/views/page-config/component/BasicLayoutEntry.vue

@@ -1,7 +1,7 @@
 <template>
   <div class="page-config-content">
     <main class="main__config">
-      <div class="card--default" @click="toLayout(ViewType.companyHomepage_PC)">
+      <div class="card--default" @click="toLayout('PC')">
         <section class="card__left">
           <img :src="PCIcon" />
         </section>
@@ -10,7 +10,7 @@
           <span class="span__card--describe"> {{ describeDefault }} PC端生效 </span>
         </section>
       </div>
-      <div class="card--default" @click="toLayout(ViewType.companyHomepage_phone)">
+      <div class="card--default" @click="toLayout('Phone')">
         <section class="card__left">
           <img :src="PhoneIcon" />
         </section>
@@ -45,12 +45,15 @@ const generateContentDefault = (layoutType: LayoutConfigType.scene | LayoutConfi
       break;
   }
 }
-const toLayout = (type: ViewType.companyHomepage_PC | ViewType.companyHomepage_phone) => {
+const toLayout = (type: 'PC' | 'Phone') => {
+  let viewType;
   if (props.layoutType === LayoutConfigType.scene) {
-    router.push(`/layout/scene-list?viewType=${type}`);
+    viewType = type === 'PC' ? ViewType.companyHomepage_PC : ViewType.companyHomepage_phone
+    router.push(`/layout/scene-list?viewType=${viewType}`);
     return;
   }
-  router.push(`/layout/camera-list?viewType=${type}`);
+  viewType = type === 'PC' ? ViewType.minimap_PC : ViewType.minimap_phone
+  router.push(`/layout/camera-list?viewType=${viewType}`);
 };
 onMounted(() => {
   generateContentDefault(props.layoutType);

+ 276 - 0
src/views/page-config/component/BasicLayoutList.vue

@@ -0,0 +1,276 @@
+<template>
+  <div class="scene-layout">
+    <header class="scene-layout__header">
+      <div class="header__btn" @click="router.back">
+        <img :src="rollback" />
+        <span>返回</span>
+      </div>
+      <el-select v-model="selectCompany" placeholder="请选择公司" class="header__select"
+        v-if="layoutType === LayoutConfigType.camera" @change="handleSelectChange">
+        <el-option v-for="company in companyList" :key="company.id" :label="company.name" :value="company.id" />
+      </el-select>
+    </header>
+    <main class="scene-layout__main">
+      <section v-if="length > 0" class="main__layout">
+        <el-card v-for="layout in layoutList" :key="layout.id" shadow="hover" class="layout-cards">
+          <div class="layout-card" @click="handleClickCompany(layout.id, layout.name)">
+            <div v-if="layout.layout">
+              <MapContainerSmall ref="mapContainerRef" :bg-image-url="layout.layout" class="content-pic" />
+            </div>
+
+            <div v-else>
+              <img :src="noLayout" />
+              <span>请添加场景布局</span>
+            </div>
+          </div>
+          <template #footer>
+            <span class="footer--default">{{ layout.name }}</span>
+            <div class="icons">
+              <el-image v-if="layout.layout" :src="preview" :preview-src-list="[layout.layout]" hide-on-click-modal
+                fit="cover" />
+
+              <img :src="edit" @click="handleClickCompany(layout.id, layout.name)" />
+            </div>
+          </template>
+        </el-card>
+      </section>
+      <section class="main__empty" v-else>
+        <img :src="empty" />
+        <span>
+          目前无内容,
+          <router-link to="/scene/workshop">
+            {{ props.layoutType === LayoutConfigType.scene ? '请先添加公司' : '请先添加车间' }}
+          </router-link>
+        </span>
+      </section>
+    </main>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import rollback from '@/assets/rollback.png';
+import empty from '@/assets/images/table/table-empty.png';
+import noLayout from '@/assets/images/page-config/no-layout.png';
+import preview from '@/assets/images/table/table-preview.png';
+import edit from '@/assets/images/table/table-edit.png';
+import router from '@/router';
+import { CompanyInfoList, LayoutInfoList } from '@/types/scene-layout/type';
+import { LayoutConfigType, ViewType } from '@/types/page-config/type';
+import {
+  getCompanyListApi,
+  getPcCompanyLayoutListApi,
+  getMobileCompanyLayoutList,
+  getWorkshopListApi,
+  getPcCameraLayoutList,
+  getMobileCameraLayoutList
+} from '@/api/scene/scene';
+import { computed, onMounted, ref } from 'vue';
+import MapContainerSmall from '@/views/page-config/component/mapContainer/MapContainerSmall.vue';
+const props = defineProps<{
+  layoutType: LayoutConfigType.scene | LayoutConfigType.camera
+}>()
+const companyList = ref<CompanyInfoList[]>([]);
+const layoutList = ref<LayoutInfoList[]>([]);
+const length = computed(() => {
+  return layoutList.value.length;
+});
+const selectCompany = ref<number>();
+const viewType = ref<number>();
+const handleSelectChange = async () => {
+  layoutList.value = await getLayoutInfoList(props.layoutType)
+  const layoutIDList = layoutList.value.map((x) => x.id);
+  getLayoutInfoImg(props.layoutType, viewType.value!, layoutIDList)
+}
+
+const handleClickCompany = (id, name) => {
+  if (props.layoutType === LayoutConfigType.scene) {
+    router.push({
+      path: '/layout/scene-config',
+      query: { companyId: id, companyName: name, viewType: viewType.value },
+    });
+    return
+  }
+  router.push({
+    path: '/layout/camera-config',
+    query: { workshopId: id, workshopName: name, viewType: viewType.value },
+  });
+};
+const getLayoutInfoList = async (layoutType: LayoutConfigType.scene | LayoutConfigType.camera) => {
+  switch (layoutType) {
+    case LayoutConfigType.scene:
+      return companyList.value
+    case LayoutConfigType.camera:
+      if (!selectCompany.value) {
+        return []
+      }
+      const res = await getWorkshopListApi({ companyId: selectCompany.value })
+      return res
+  }
+}
+const scenePlatformApiMap = {
+  [ViewType.companyHomepage_PC]: getPcCompanyLayoutListApi,
+  [ViewType.companyHomepage_phone]: getMobileCompanyLayoutList,
+};
+const cameraPlatformApiMap = {
+  [ViewType.minimap_PC]: getPcCameraLayoutList,
+  [ViewType.minimap_phone]: getMobileCameraLayoutList,
+};
+const getLayoutInfoImg = (layoutType: LayoutConfigType.scene | LayoutConfigType.camera, viewType: number, idList: number[]) => {
+  switch (layoutType) {
+    case LayoutConfigType.scene:
+      scenePlatformApiMap[viewType]({ companyIds: idList }).then((res) => {
+        res.map((companyWithLayout) => {
+          layoutList.value.find((company) => company.id === companyWithLayout.targetId)!.layout =
+            JSON.parse(companyWithLayout.layout).bgInfo.img;
+        });
+      });
+      break;
+    case LayoutConfigType.camera:
+      cameraPlatformApiMap[viewType]({ workshopIdList: idList }).then((res) => {
+        res.map((workshopWithLayout) => {
+          layoutList.value.find((workshop) => workshop.id === workshopWithLayout.targetId)!.layout =
+            JSON.parse(workshopWithLayout.layout).bgImgUrl;
+        });
+      });
+      break;
+  }
+}
+
+onMounted(async () => {
+  viewType.value = Number(router.currentRoute.value.query.viewType);
+  companyList.value = await getCompanyListApi();
+  if (!companyList.value) return;
+  selectCompany.value = companyList.value[0].id;
+  layoutList.value = await getLayoutInfoList(props.layoutType)
+  const layoutIDList = layoutList.value.map((x) => x.id);
+  getLayoutInfoImg(props.layoutType, viewType.value, layoutIDList)
+});
+</script>
+
+<style lang="scss" scoped>
+.footer--default {
+  opacity: 0.45;
+}
+
+.scene-layout {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: calc(100vh - 64px);
+
+  &__header {
+    display: flex;
+    gap: 50px;
+    align-items: center;
+    width: inherit;
+    height: 54px;
+    padding-left: 23px;
+    background: #ffffff;
+    border-top: 1px solid rgba(0, 0, 0, 0.1);
+    box-shadow: 0px 0px 6px 0px rgba(0, 0, 0, 0.12);
+
+    .header__btn {
+      display: flex;
+      gap: 10px;
+      cursor: pointer;
+    }
+
+    .header__select {
+      width: 200px;
+    }
+
+    img {
+      width: 14px;
+      height: 14px;
+    }
+
+    span {
+      line-height: 14px;
+
+      &:hover {
+        text-shadow: 5px 5px 6px rgba(0, 0, 0, 0.12);
+      }
+    }
+  }
+
+  &__main {
+    margin-top: 12px;
+    width: inherit;
+    height: calc(100vh - 64px - 54px - 12px);
+    background: #ffffff;
+    box-shadow: 0px 0px 6px 0px rgba(0, 0, 0, 0.12);
+    border-radius: 6px;
+  }
+
+  .main__layout {
+    display: flex;
+    gap: 25px;
+    flex-wrap: wrap;
+    align-content: flex-start;
+    width: inherit;
+    height: inherit;
+    padding: 22px 24px 22px 24px;
+  }
+
+  .layout-cards {
+    width: 216px;
+    height: 190px;
+
+    .layout-card div {
+      display: flex;
+      flex-direction: column;
+      justify-content: center;
+      align-items: center;
+      gap: 4px;
+    }
+  }
+
+  :deep(.el-card__body) {
+    width: inherit;
+    height: 145px;
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    align-items: center;
+    padding: 0;
+    background: #f5f5f5;
+    cursor: pointer;
+  }
+
+  :deep(.el-card__footer) {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    width: inherit;
+    height: 45px;
+    padding: 0 12px 0 12px;
+
+    .icons {
+      display: flex;
+      gap: 10px;
+
+      img,
+      el-image {
+        cursor: pointer;
+        opacity: 0.4;
+        transition: opacity 0.5s ease-in-out;
+
+        &:hover {
+          opacity: 1;
+        }
+      }
+    }
+  }
+
+  .main__empty {
+    display: flex;
+    flex-direction: column;
+    gap: 2px;
+    justify-content: center;
+    align-items: center;
+    width: inherit;
+    height: inherit;
+  }
+}
+</style>

+ 77 - 79
src/views/page-config/component/mapContainer/MapContainerSmall.vue

@@ -6,103 +6,101 @@
         <v-group v-for="item in props.showShops" :key="item.id" :config="getGroupConfig(item)">
           <LabelItem :shop="item" />
         </v-group>
-        <!-- <v-transformer ref="transformerRef" :config="transformerConfig" /> -->
       </v-layer>
     </v-stage>
   </div>
 </template>
 
 <script setup lang="ts">
-  import { ref, watch } from 'vue';
-  import LabelItem from './LabelItem.vue';
-  // import { storeToRefs } from 'pinia';
-  import { MapWorkShopInfoItem } from '../../stores/useMapEditor';
-  import Konva from 'konva';
-  import { useGlobSetting } from '@/hooks/setting';
-  import urlJoin from 'url-join';
+import { ref, watch } from 'vue';
+import LabelItem from './LabelItem.vue';
+import { MapWorkShopInfoItem } from '../../stores/useMapEditor';
+import Konva from 'konva';
+import { useGlobSetting } from '@/hooks/setting';
+import urlJoin from 'url-join';
 
-  const globSetting = useGlobSetting();
-  const props = defineProps<{ showShops?; bgImageUrl? }>();
-  const bgImage = ref<HTMLImageElement>(new Image());
-  const stageRef = ref<Konva.Stage>();
-  const layerRef = ref<Konva.Layer>();
+const globSetting = useGlobSetting();
+const props = defineProps<{ showShops?; bgImageUrl?}>();
+const bgImage = ref<HTMLImageElement>(new Image());
+const stageRef = ref<Konva.Stage>();
+const layerRef = ref<Konva.Layer>();
 
-  const stageSize = ref({
-    width: 214,
-    height: 120,
-  });
+const stageSize = ref({
+  width: 214,
+  height: 120,
+});
 
-  const bgConfig = ref({
-    width: 1920,
-    height: 1080,
-    image: bgImage.value,
-    name: 'bg',
-  });
+const bgConfig = ref({
+  width: 1920,
+  height: 1080,
+  image: bgImage.value,
+  name: 'bg',
+});
 
-  const getGroupConfig = (shop: MapWorkShopInfoItem) => {
-    return {
-      x: shop.x,
-      y: shop.y,
-      scaleX: shop.scaleX,
-      scaleY: shop.scaleY,
-      id: shop.id + '',
-      name: 'group',
-    };
+const getGroupConfig = (shop: MapWorkShopInfoItem) => {
+  return {
+    x: shop.x,
+    y: shop.y,
+    scaleX: shop.scaleX,
+    scaleY: shop.scaleY,
+    id: shop.id + '',
+    name: 'group',
   };
+};
 
-  const addBg = () => {
-    return new Promise((resolve) => {
-      const imgUrl = props.bgImageUrl.includes('skyeye-file-upload')
-        ? props.bgImageUrl
-        : urlJoin(globSetting.imgUrl!, props.bgImageUrl);
+const addBg = () => {
+  return new Promise((resolve) => {
+    const imgUrl = props.bgImageUrl.includes('skyeye-file-upload')
+      ? props.bgImageUrl
+      : urlJoin(globSetting.imgUrl!, props.bgImageUrl);
 
-      const tempImg = new Image();
-      tempImg.src = imgUrl;
-      tempImg.onload = () => {
-        bgConfig.value.image = tempImg;
-      };
-      resolve(null);
-    });
-  };
+    const tempImg = new Image();
+    tempImg.src = imgUrl;
+    tempImg.onload = () => {
+      bgConfig.value.image = tempImg;
+    };
+    resolve(null);
+  });
+};
 
-  const zoomOut = () => {
-    const stage = stageRef.value!.getStage();
-    if (!stage) return;
+const zoomOut = () => {
+  const stage = stageRef.value!.getStage();
+  if (!stage) return;
 
-    const targetWidth = 214;
-    const targetHeight = 120;
-    const fixWidth = 1920;
-    const fixHeight = 1080;
-    //得到缩放比
-    const scaleX = targetWidth / fixWidth;
-    const scaleY = targetHeight / fixHeight;
+  const targetWidth = 214;
+  const targetHeight = 120;
+  const fixWidth = 1920;
+  const fixHeight = 1080;
+  //得到缩放比
+  const scaleX = targetWidth / fixWidth;
+  const scaleY = targetHeight / fixHeight;
 
-    //背景图片缩小
-    bgConfig.value.width = targetWidth;
-    bgConfig.value.height = targetHeight;
+  //背景图片缩小
+  bgConfig.value.width = targetWidth;
+  bgConfig.value.height = targetHeight;
 
-    //缩小group
-    const children = stage.find('.group');
-    children.forEach((node) => {
-      const newScaleX = node.attrs.scaleX * scaleX;
-      const newScaleY = node.attrs.scaleY * scaleY;
-      node.attrs.scaleX = newScaleX;
-      node.attrs.scaleY = newScaleY;
-      node.x(node.x() * scaleX);
-      node.y(node.y() * scaleY);
-    });
-    stage.batchDraw();
-  };
+  //缩小group
+  const children = stage.find('.group');
+  children.forEach((node) => {
+    const newScaleX = node.attrs.scaleX * scaleX;
+    const newScaleY = node.attrs.scaleY * scaleY;
+    node.attrs.scaleX = newScaleX;
+    node.attrs.scaleY = newScaleY;
+    node.x(node.x() * scaleX);
+    node.y(node.y() * scaleY);
+  });
+  stage.batchDraw();
+};
 
-  watch(
-    () => props.bgImageUrl,
-    () => {
-      addBg().then(() => {
-        zoomOut();
-      });
-    },
-    { immediate: true },
-  );
+watch(
+  () => props.bgImageUrl,
+  () => {
+    addBg().then(() => {
+      zoomOut();
+    });
+  },
+  { immediate: true },
+);
 </script>
 
 <style scoped></style>