Просмотр исходного кода

Merge branch 'staffNoFix' into 'master'

小地图配置vue-konva优化

See merge request tian-group/skyeye-admin-fe!33
孙宏耀 2 лет назад
Родитель
Сommit
6a4535064d

Разница между файлами не показана из-за своего большого размера
+ 8615 - 0
pnpm-lock.yaml


+ 18 - 0
src/hooks/useSceneInfos.ts

@@ -60,6 +60,24 @@ export function useSceneInfos() {
 
   const getCameraList = (workshop: number) => {
     return getCamerasByWorkSpace({ workshopId: workshop }).then((res) => {
+      res[0]?.cameraList?.push(
+        {
+          name: 'C204',
+          code: 'C204',
+          cameraIp: '192.168.1.203',
+          remark: '',
+          status: 0,
+          pushstreamIp: 'http://192.168.1.138:8080/live/C203.flv',
+        },
+        {
+          name: 'C205',
+          code: 'C205',
+          cameraIp: '192.168.1.203',
+          remark: '',
+          status: 0,
+          pushstreamIp: 'http://192.168.1.138:8080/live/C203.flv',
+        },
+      );
       return res;
     });
   };

+ 2 - 1
src/main.ts

@@ -8,6 +8,7 @@ import VueKonva from 'vue-konva';
 
 import { createApp } from 'vue';
 import App from './App.vue';
+import VueKonva from 'vue-konva';
 import router, { setupRouter } from './router';
 import { setupStore } from '@/store';
 import 'virtual:svg-icons-register';
@@ -40,7 +41,7 @@ async function bootstrap() {
 
   // 路由准备就绪后挂载APP实例
   await router.isReady();
-
+  app.use(VueKonva);
   app.mount('#app', true);
 }
 

+ 0 - 71
src/views/map-config/mini-map/MapBase/CameraGroup-bak.ts

@@ -1,71 +0,0 @@
-import { fabric } from 'fabric';
-import cameraActiveImg from '@/assets/camera/camera-active.png';
-import cameraImg from '@/assets/camera/camera.png';
-import favoritesImg from '@/assets/camera/favorites.png';
-import { getRandomPosition } from './utils';
-import { CameraImage } from './types';
-
-class CameraGroup extends fabric.Group {
-  g: fabric.Group | null = null;
-  cameraImg: CameraImage | null = null;
-  favImg: CameraImage | null = null;
-  cameraId = '';
-
-  constructor() {
-    super();
-    fabric.Image.fromURL(cameraActiveImg, (cImg) => {
-      cImg.set({
-        left: 0,
-        top: 0,
-      });
-      this.cameraImg = cImg;
-      fabric.Image.fromURL(favoritesImg, (favImg) => {
-        favImg.set({
-          left: 50,
-          top: 0,
-        });
-        this.favImg = favImg as CameraImage;
-        this.g = new fabric.Group([cImg, favImg]);
-      });
-    });
-  }
-
-  init() {}
-
-  /** 提供copy功能,每次新建的时候执行copy就行了 */
-  clone(): Promise<CameraGroup> {
-    console.log('clone');
-    return new Promise((resolve) => {
-      this.g?.clone((e) => {
-        console.log('clone', e);
-        resolve(e as CameraGroup);
-      });
-    });
-  }
-
-  setSelected() {
-    this.cameraImg?.setSrc(cameraActiveImg);
-  }
-
-  setUnSelected() {
-    this.cameraImg?.setSrc(cameraImg);
-  }
-  /** 设为默认摄像头 */
-  setDefault() {
-    this.favImg?.set('visible', true);
-  }
-  /** 取消默认摄像头 */
-  cancelDefault() {
-    this.favImg?.set('visible', false);
-  }
-
-  setAttr(attr: { cameraId: string; left: number; top: number }) {
-    if (attr.cameraId) {
-      this.cameraId = attr.cameraId;
-    }
-    this.g?.set(attr);
-    return this.g;
-  }
-}
-
-export default CameraGroup;

+ 0 - 88
src/views/map-config/mini-map/MapBase/CameraGroup.ts

@@ -1,88 +0,0 @@
-import { fabric } from 'fabric';
-import cameraActiveImg from '@/assets/camera/camera-active.png';
-import cameraImg from '@/assets/camera/camera.png';
-import favoritesImg from '@/assets/camera/favorites.png';
-import { CameraImage } from './types';
-
-class CameraGroup extends fabric.Group {
-  g: fabric.Group;
-  cameraImg: CameraImage | null = null;
-  favImg: CameraImage | null = null;
-  cameraId = '';
-
-  constructor() {
-    super();
-    this.init();
-  }
-
-  init() {
-    fabric.Image.fromURL(cameraActiveImg, (cImg) => {
-      cImg.set({
-        left: 0,
-        top: 0,
-        width: 100,
-        height: 100,
-        imageName: 'cameraImage',
-      });
-      console.log('cameraActiveImg', cImg);
-      this.cameraImg = cImg;
-      fabric.Image.fromURL(favoritesImg, (favImg) => {
-        favImg.set({
-          width: 50,
-          height: 50,
-          left: 50,
-          top: 0,
-          imageName: 'favImage',
-        });
-        this.favImg = favImg as CameraImage;
-        this.g = new fabric.Group([cImg, favImg]);
-      });
-    });
-  }
-
-  /** 提供copy功能,每次新建的时候执行copy就行了 */
-  clone(): Promise<CameraGroup> {
-    console.log('clone');
-    console.log('this', this);
-    return new Promise((resolve) => {
-      this.g.clone((e) => {
-        console.log('clone', e);
-        const newG = e as fabric.Group;
-        const newGroup = new CameraGroup();
-        const cameraImg = newG.getObjects().find((x) => x.imageName === 'cameraImage');
-        const favImage = newG.getObjects().find((x) => x.imageName === 'favImage');
-        newGroup.cameraImg =                            ;
-        newGroup.favImg = favImage;
-        newGroup.g = e;
-        resolve(newGroup as CameraGroup);
-      });
-    });
-  }
-
-  setSelected() {
-    
-    this.cameraImg?.setSrc(cameraActiveImg);
-  }
-
-  setUnSelected() {
-    this.cameraImg?.setSrc(cameraImg);
-  }
-  /** 设为默认摄像头 */
-  setDefault() {
-    this.favImg?.set('visible', true);
-  }
-  /** 取消默认摄像头 */
-  cancelDefault() {
-    this.favImg?.set('visible', false);
-  }
-
-  setAttr(attr: { cameraId: string; left: number; top: number }) {
-    if (attr.cameraId) {
-      this.cameraId = attr.cameraId;
-    }
-    this.g?.set(attr);
-    return this.g;
-  }
-}
-
-export default new CameraGroup();

+ 0 - 216
src/views/map-config/mini-map/MapBase/CameraMapBak.ts

@@ -1,216 +0,0 @@
-import { fabric } from 'fabric';
-import { ref } from 'vue';
-import cameraActiveImg from '@/assets/camera/camera-active.png';
-import cameraImg from '@/assets/camera/camera.png';
-import favoritesImg from '@/assets/camera/favorites.png';
-import { CameraImage, MapData, OnMoving, OnRightClick, OnSelect, isCanvas } from './types';
-import { fabricSetting } from './fabricSetting';
-import { getRandomPosition } from './utils';
-// import templateGroup from './CameraGroup';
-import { createGroup, toggleGroupSelected, toggleCameraDefault } from './CameraStarGroup';
-
-fabricSetting();
-
-class CameraMap {
-  public canvas = ref<fabric.Canvas | null>();
-  private onSelect: OnSelect;
-  private onRightClick: OnRightClick;
-  private onMoving: OnMoving;
-
-  constructor(param: {
-    canvasId: string;
-    onSelect: OnSelect;
-    onRightClick: OnRightClick;
-    onMoving: OnMoving;
-  }) {
-    this.canvas.value = new fabric.Canvas(param.canvasId, {
-      fireRightClick: true, // 启用右键,button的数字为3
-      stopContextMenu: true, // 禁止默认右键菜单
-    });
-    this.addListener();
-    this.onSelect = param.onSelect;
-    this.onRightClick = param.onRightClick;
-    this.onMoving = param.onMoving;
-    window.canvas = this.canvas.value;
-  }
-
-  /** 监听点击事件 */
-  private addListener() {
-    const canvas = this.canvas.value;
-    if (!isCanvas(canvas)) return;
-
-    canvas.on('mouse:down', (options) => {
-      const target = options.target as CameraImage;
-      const cameraId = target?.cameraId;
-      // console.log('当前选中的id是', cameraId);
-      console.log('mouse:down');
-      console.log(options);
-
-      // 判断:右键,且在元素上右键
-      // opt.button: 1-左键;2-中键;3-右键
-      // 在画布上点击:opt.target 为 null
-      if (options.button === 3 && options.target) {
-        this.onRightClick(options);
-        return;
-      }
-
-      this.setAllGroupsUnselected();
-      if (!cameraId || !target) {
-        this.onSelect(null);
-        return;
-      }
-      toggleGroupSelected(target, true).then(() => {
-        canvas.renderAll();
-      });
-      this.onSelect(target);
-    });
-  }
-
-  private setAllGroupsUnselected() {
-    const canvas = this.canvas;
-    if (!isCanvas(canvas)) return;
-    canvas.getObjects('group').forEach((object) => {
-      // if (object === options.target) return;
-      toggleGroupSelected(object, false).then((res) => {
-        canvas.renderAll();
-      });
-    });
-  }
-
-  /** 上传背景图 */
-  public uploadBg(imgUrl: string) {
-    const canvas = this.canvas.value;
-    if (!isCanvas(canvas)) return;
-    fabric.Image.fromURL(imgUrl, (img) => {
-      console.log('image', img);
-      canvas!.setWidth(img.width!);
-      canvas!.setHeight(img.height!);
-      img.lockScalingX = true;
-      img.lockScalingY = true;
-      // 设置背景图
-      canvas.setBackgroundImage(img, this.canvas.value!.renderAll.bind(this.canvas));
-    });
-  }
-
-  /** 将所有的摄像头都设置为非激活状态 */
-  private setAllCameraUnActive() {
-    const canvas = this.canvas.value;
-    if (!isCanvas(canvas)) return;
-    canvas.forEachObject((object) => {
-      (object as fabric.Image).setSrc(cameraImg, () => {
-        canvas.renderAll();
-      });
-    });
-  }
-
-  /** 增加一个摄像头 */
-  public addCamera(cameraId: string): Promise<CameraImage> {
-    const canvas = this.canvas.value;
-    if (!isCanvas(canvas)) return Promise.reject();
-    // eslint-disable-next-line @typescript-eslint/no-this-alias
-    const that = this;
-    return new Promise((resolve) => {
-      fabric.Image.fromURL(cameraActiveImg, (cImg) => {
-        const cameraImg = ref(cImg as unknown as CameraImage);
-        this.setAllCameraUnActive();
-        cameraImg.value.set({
-          left: getRandomPosition(),
-          top: getRandomPosition(),
-          cameraId,
-        });
-        cameraImg.value.lockScalingX = true;
-        cameraImg.value.lockScalingY = true;
-        canvas.add(cameraImg.value);
-        cameraImg.value.on('moving', function (e) {
-          console.log('move', e);
-          that.onMoving(e);
-        });
-        resolve(cameraImg);
-      });
-    });
-  }
-
-  /** 删除一个摄像头 */
-  public removeCamera(cameraImage: CameraImage) {
-    const canvas = this.canvas.value;
-    if (!isCanvas(canvas)) return;
-    console.log('removeCamera', cameraImage);
-    canvas.remove(cameraImage);
-  }
-
-  /** 导出JSON格式 */
-  public toJSON() {
-    const canvas = this.canvas.value;
-    if (!isCanvas(canvas)) return;
-    const initialJSON = canvas.toJSON(['cameraId']);
-    /** toJSON返回值的类型它写错了,应该是有backgroundImage的 */
-    const { src, type, version, width, height, left, top, angle } =
-      (initialJSON as any).backgroundImage || {};
-
-    const newObjects = initialJSON.objects.map((item) => {
-      return {
-        type: item.type,
-        width: item.width,
-        height: item.height,
-        left: item.left,
-        top: item.top,
-        angle: item.angle,
-        cameraId: (item as CameraImage).cameraId,
-      };
-    });
-    const newJson = {
-      version: initialJSON.version,
-      backgroundImage: { src, type, version, width, height, left, top, angle },
-      objects: newObjects,
-    };
-    return newJson;
-  }
-
-  /** 从json中加载 */
-  public loadFromJSON(json: MapData): Promise<void> {
-    const canvas = this.canvas.value;
-    if (!isCanvas(canvas)) return Promise.reject();
-    return new Promise((resolve) => {
-      const { width, height } = json.backgroundImage;
-      canvas.setWidth(width);
-      canvas.setHeight(height);
-      const objects = json.objects.map((item) => {
-        return {
-          ...item,
-          src: cameraImg,
-        };
-      });
-      canvas.loadFromJSON({ ...json, objects }, () => {
-        resolve();
-      });
-    });
-  }
-
-  /** 更新摄像头的渲染 */
-  public renderCamera() {
-    const canvas = this.canvas.value;
-    if (!isCanvas(canvas)) return;
-    canvas.renderAll();
-  }
-
-  public clear() {
-    this.canvas.value?.clear();
-  }
-
-  /** 是否已经存在这个cameraId */
-  public hasCamera(cameraId: string) {
-    const canvas = this.canvas.value;
-    if (!isCanvas(canvas)) return;
-    const cameraIds = canvas
-      .toJSON(['cameraId'])
-      .objects.map((item) => (item as CameraImage).cameraId);
-    return cameraIds.includes(cameraId);
-  }
-
-  /** 根据cameraId查找某个元素 */
-  public getCameraById(cameraId: string) {
-    return this.canvas.value?.getObjects().find((x) => (x as CameraImage).cameraId === cameraId);
-  }
-}
-
-export default CameraMap;

+ 0 - 73
src/views/map-config/mini-map/MapBase/CameraPreview.vue

@@ -1,73 +0,0 @@
-<template>
-  <div>
-    <div style="overflow: auto; position: relative">
-      <canvas width="400" height="400" ref="canvasRef" style="border: 1px solid #ccc"></canvas>
-      <DefaultCameraIcon :position="favPosition" />
-    </div>
-  </div>
-</template>
-<script lang="ts" setup>
-  import { fabric } from 'fabric';
-  import { onMounted, ref, watch } from 'vue';
-  import cameraImg from '@/assets/camera/camera.png';
-  import { CameraImage, MapData } from './types';
-  import DefaultCameraIcon from './DefaultCameraIcon.vue';
-  import { getFavPositionByCamera } from './utils';
-
-  const props = defineProps<{ json: MapData }>();
-
-  let canvas;
-  const canvasRef = ref<HTMLCanvasElement>();
-
-  const favPosition = ref<{ left: number; top: number } | null>(null);
-
-  const createMap = () => {
-    if (!canvasRef.value) return;
-    canvas = new fabric.Canvas(canvasRef.value, {
-      fireRightClick: true, // 启用右键,button的数字为3
-      stopContextMenu: true, // 禁止默认右键菜单
-    });
-    canvas.selectable = false;
-    // window.cvs = canvas;
-  };
-
-  onMounted(() => {
-    createMap();
-  });
-
-  watch(
-    () => props.json,
-    () => {
-      const json = props.json;
-      console.log('props.json', props.json);
-      if (json) {
-        const { width, height } = json.backgroundImage;
-        canvas?.setWidth(width);
-        canvas?.setHeight(height);
-        const objects = json.objects.map((item) => {
-          return {
-            ...item,
-            src: cameraImg,
-            selectable: false,
-            hasControls: false,
-            hasBoards: false,
-          };
-        });
-        canvas?.loadFromJSON({ ...json, objects }, () => {
-          const defaultCamera = getCameraById(json.defaultCameraId);
-          if (defaultCamera) {
-            favPosition.value = getFavPositionByCamera(defaultCamera);
-          }
-        });
-      }
-    },
-    { deep: true },
-  );
-
-  const getCameraById = (cameraId: string) => {
-    return canvas
-      ?.getObjects()
-      .find((x) => (x as CameraImage).cameraId === cameraId) as CameraImage;
-  };
-</script>
-<style scoped></style>

+ 0 - 68
src/views/map-config/mini-map/MapBase/CameraStarGroup.ts

@@ -1,68 +0,0 @@
-import { fabric } from 'fabric';
-import cameraActiveImg from '@/assets/camera/camera-active.png';
-import cameraImg from '@/assets/camera/camera.png';
-import favoritesImg from '@/assets/camera/favorites.png';
-import { CameraImage } from './types';
-
-const cameraImageName = 'cameraImage';
-const favImageName = 'favImageImage';
-
-const cameraInfo = { width: 60, height: 40 };
-const favInfo = { width: 10, height: 10 };
-
-export function createGroup(): Promise<fabric.Group> {
-  return new Promise((resolve) => {
-    fabric.Image.fromURL(cameraActiveImg, (cImg) => {
-      cImg.set({
-        left: 0,
-        top: 0,
-        width: cameraInfo.width,
-        height: cameraInfo.height,
-        imageName: cameraImageName,
-      });
-      console.log('cameraActiveImg', cImg);
-
-      fabric.Image.fromURL(favoritesImg, (favImg) => {
-        favImg.set({
-          width: favInfo.width,
-          height: favInfo.height,
-          left: 40,
-          top: -10,
-          visible: false,
-          imageName: favImageName,
-        });
-        resolve(
-          new fabric.Group([cImg, favImg], {
-            width: cameraInfo.width,
-            height: cameraInfo.height,
-            // 不要缓存,否则无法修改图片地址
-            objectCaching: false,
-          }),
-        );
-      });
-    });
-  });
-}
-
-/** 设置是否是选中后的效果 */
-export function toggleGroupSelected(group: fabric.Group, isSelected: boolean) {
-  return new Promise((resolve, reject) => {
-    const groupCamera = group.getObjects()[0] as fabric.Image;
-    // .find((item) => item.imageName === cameraImageName) as fabric.Image;
-    if (!groupCamera) {
-      reject();
-      return;
-    }
-    const src = isSelected ? cameraActiveImg : cameraImg;
-    groupCamera.setSrc(src, () => {
-      resolve();
-    });
-  });
-}
-
-/** 设置是否为默认摄像头 */
-export function toggleCameraDefault(group: fabric.Group, visible: boolean) {
-  const star = group.getObjects()[1];
-  if (!star) return;
-  star.set({ visible });
-}

+ 0 - 21
src/views/map-config/mini-map/MapBase/DefaultCameraIcon.vue

@@ -1,21 +0,0 @@
-<template>
-  <!-- 默认选中图片的icon -->
-  <img
-    v-if="props.position"
-    :src="favIcon"
-    class="defaultCameraImg"
-    :style="{ left: props.position?.left + 'px', top: props.position?.top + 'px' }"
-  />
-</template>
-<script lang="ts" setup>
-  import favIcon from '@/assets/camera/favorites.png';
-
-  const props = defineProps<{ position: { left: number; top: number } | null }>();
-</script>
-<style scoped>
-  .defaultCameraImg {
-    position: absolute;
-    width: 15px;
-    /* height: 50px; */
-  }
-</style>

+ 379 - 0
src/views/map-config/mini-map/MapBase/KonvaMap.vue

@@ -0,0 +1,379 @@
+<template>
+  <div>
+    <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"
+          :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
+      @click="setDefaultCamera"
+      v-show="defaultShow"
+      class="opt-container"
+      :style="{ position: 'absolute', left: posX + 'px', top: posY + 'px' }"
+      ><div class="opt-item" :class="{ disabled: disabledSet }">设为默认相机</div></div
+    >
+    <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 } from 'element-plus';
+  import { useGlobSetting } from '@/hooks/setting';
+  import DefaultTip from '../components/DefaultTip.vue';
+  import urlJoin from 'url-join';
+  import cameraImgSrc from '@/assets/camera/cameraImg.png';
+  import favoritesImgSrc from '@/assets/camera/favorites.png';
+  import { TipPositionEnum, camerasGroupType } from '../type';
+
+  const globSetting = useGlobSetting();
+
+  const emit = defineEmits(['changeDefaultCamera', 'sendCameraId']);
+
+  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 bgImgUrl = ref<string | null>('');
+  const cameras = ref<camerasGroupType[]>([]);
+  //默认相机id
+  const defaultCameraId = ref('');
+  const tipShow = ref(false);
+  const disabledSet = ref(false);
+
+  //标签
+  const layer = ref();
+  const transformer = ref();
+  const defaultIcon = ref();
+
+  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 });
+
+  //背景大小变换
+  const resizeContainer = (width, height) => {
+    stageConfig.value.width = width;
+    stageConfig.value.height = height;
+  };
+
+  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;
+        }
+      } else {
+        posTipY.value -= 61;
+        posTipX.value -= 43;
+      }
+    }
+  };
+  // 还原浏览器默认鼠标事件
+  const handleMouseLeave = () => {
+    document.oncontextmenu = () => {
+      return true;
+    };
+    tipShow.value = false;
+  };
+
+  const handleDragStart = () => {
+    tipShow.value = false;
+  };
+
+  const handleStageClick = (e: any) => {
+    defaultShow.value = false;
+    disabledSet.value = false;
+    document.oncontextmenu = () => {
+      return false;
+    };
+    const parent = e.target.parent;
+    //判断是否点击相机组
+    if (parent.hasName('group')) {
+      lastClickedGroupId.value = parent.id();
+      // 判断是否为右键点击
+      if (e.evt.button === 2) {
+        lastClickedGroupId.value = parent.id();
+        posX.value = e.evt.offsetX + 20;
+        posY.value = e.evt.offsetY;
+        disabledSet.value = defaultCameraId.value === parent.id();
+        defaultShow.value = true;
+      }
+    } 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();
+  };
+
+  //添加相机
+  const addCamera = (id: string) => {
+    const existingCamera = cameras.value.find((camera) => camera.id === id);
+    if (existingCamera) return;
+    // const camImg = new Image();
+    // camImg.src = cameraImgSrc;
+    const config = {
+      width: 52,
+      height: 37,
+      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;
+    }
+  };
+
+  //设置默认相机
+  const setDefaultCamera = () => {
+    //选中的相机id号
+    defaultCameraId.value = lastClickedGroupId.value!;
+    cameras.value.forEach((item) => {
+      if (item.id === defaultCameraId.value) {
+        item.isDefault = true;
+      } else {
+        item.isDefault = false;
+      }
+    });
+    defaultShow.value = false;
+  };
+
+  watch(
+    lastClickedGroupId,
+    () => {
+      emit('changeDefaultCamera', lastClickedGroupId.value);
+    },
+    { immediate: true },
+  );
+
+  watch(
+    () => cameras.value,
+    () => {
+      emit('sendCameraId', cameras.value);
+    },
+    { immediate: true, deep: true },
+  );
+
+  //添加背景
+  const addBg = (imgBg) => {
+    bgImgUrl.value = imgBg;
+    if (!bgImgUrl.value) {
+      bgImg.src = null as any as string;
+      return;
+    }
+    bgImg.src = urlJoin(globSetting.imgUrl!, imgBg) as string;
+    bgImg.onload = () => {
+      bgConfig.value.width = bgImg.width;
+      bgConfig.value.height = bgImg.height;
+      resizeContainer(bgImg.width, bgImg.height);
+    };
+  };
+
+  //保存布局
+  const saveLayout = () => {
+    const stage = transformer.value.getNode().getStage();
+    const groups = stage.find('.group');
+    const camerasList = groups.map((item, index) => {
+      cameras.value[index].groupConfig.x = Math.round(item.attrs.x | 0);
+      cameras.value[index].groupConfig.y = Math.round(item.attrs.y | 0);
+      cameras.value[index].groupConfig.rotation = Number((item.attrs.rotation | 0).toFixed(2));
+      cameras.value[index].groupConfig.scaleX = Number((item.attrs.scaleX | 1).toFixed(1));
+      cameras.value[index].groupConfig.scaleY = Number((item.attrs.scaleY | 1).toFixed(1));
+      cameras.value[index].config.url = cameraImgSrc;
+      return cameras.value[index];
+    });
+    cameras.value = camerasList;
+    const layout = {
+      stageConfig: stageConfig.value,
+      bgConfig: bgConfig.value,
+      bgImgUrl: bgImgUrl.value,
+      defaultCameraId: defaultCameraId.value,
+      cameraList: cameras.value,
+    };
+    return JSON.stringify(layout);
+  };
+
+  //删除相机
+  const handleKeyDown = (e) => {
+    if (e.keyCode === 46 || e.code === 'Delete') {
+      if (lastClickedGroupId.value === defaultCameraId.value) {
+        ElMessage.error({
+          message: '无法删除默认相机',
+        });
+        return;
+      }
+      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([]);
+    }
+  };
+
+  //重置
+  const resetMap = () => {
+    bgImgUrl.value = null;
+    addBg('');
+    cameras.value = [];
+    lastClickedGroupId.value = null;
+    defaultCameraId.value = '';
+  };
+
+  /** 导入布局json */
+  const createMap = (layout) => {
+    addBg(layout.bgImgUrl);
+    stageConfig.value = layout.stageConfig;
+    // const camImg = new Image();
+    // camImg.src = cameraImgSrc;
+    defaultCameraId.value = layout.defaultCameraId;
+    layout.cameraList = layout.cameraList?.map((item) => {
+      item.config.image = camImg;
+      return item;
+    });
+    cameras.value = layout.cameraList;
+  };
+
+  defineExpose({ addBg, createMap, resetMap, addCamera, resizeContainer, saveLayout });
+
+  onMounted(() => {
+    window.addEventListener('keydown', handleKeyDown);
+    camImg.src = cameraImgSrc;
+  });
+
+  onBeforeUnmount(() => {
+    window.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 {
+    background-color: #f1f2f5;
+    color: #bcbdc0;
+    cursor: not-allowed;
+  }
+</style>

+ 0 - 58
src/views/map-config/mini-map/MapBase/fabricSetting.ts

@@ -1,58 +0,0 @@
-import { fabric } from 'fabric';
-// import favoritesImg from '@/assets/camera/favorites.png';
-
-export function fabricSetting() {
-  fabric.Object.prototype.padding = 10;
-
-  // 修改控制点的形状,默认为`rect`矩形,可选的值还有`circle`圆形
-  // fabric.Object.prototype.cornerStyle = 'circle';
-
-  // 修改控制点的大小为10px
-  fabric.Object.prototype.cornerSize = 8;
-
-  fabric.Object.prototype.controls.mtr.withConnection = true;
-  // 单独修改旋转控制点距离主体的纵向距离为-20px
-  fabric.Object.prototype.controls.mtr.offsetY = -20;
-
-  // 单独修改旋转控制点,光标移动到该点上时的样式为`pointer`,一个手的形状
-  fabric.Object.prototype.controls.mtr.cursorStyle = 'pointer';
-  /** 边框颜色 */
-  fabric.Object.prototype.borderColor = '#5687f1';
-  // 修改控制点的填充色为白色
-  fabric.Object.prototype.cornerColor = '#5687f1';
-
-  /** 隐藏x y轴的变形,只能等比例变形 */
-  fabric.Object.prototype.controls.mr.visible = false;
-  fabric.Object.prototype.controls.ml.visible = false;
-  fabric.Object.prototype.controls.mt.visible = false;
-  fabric.Object.prototype.controls.mb.visible = false;
-
-  window.fabric = fabric;
-}
-
-// // 渲染元素的icon按钮
-// function renderIcon(icon) {
-//   return function (ctx, left, top, styleOverride, fabricObject) {
-//     const size = this.cornerSize;
-//     ctx.save();
-//     ctx.translate(left, top);
-//     ctx.rotate(fabric.util.degreesToRadians(fabricObject.angle));
-//     ctx.drawImage(icon, -size / 2, -size / 2, size, size);
-//     ctx.restore();
-//   };
-// }
-// function setControlIcon() {
-//   const favImg = document.createElement('img');
-//   favImg.src = favoritesImg;
-//   // 删除按钮控件
-//   fabric.Object.prototype.controls.favIcon = new fabric.Control({
-//     x: 0.5,
-//     y: -0.5,
-//     offsetY: -16,
-//     offsetX: 26,
-//     cursorStyle: 'pointer',
-//     // mouseUpHandler: deleteObject,
-//     render: renderIcon(favImg),
-//     cornerSize: 24,
-//   });
-// }

+ 0 - 42
src/views/map-config/mini-map/MapBase/mapData.json

@@ -1,42 +0,0 @@
-{
-  "version": "5.3.0",
-  "backgroundImage": {
-    "src": "http://127.0.0.1:5174/src/assets/img/canvasBg.png?t=1700735801636",
-    "type": "image",
-    "version": "5.3.0",
-    "width": 800,
-    "height": 409,
-    "left": 0,
-    "top": 0,
-    "angle": 0
-  },
-  "objects": [
-    {
-      "type": "image",
-      "width": 30,
-      "height": 19,
-      "left": 34.77,
-      "top": 15.88,
-      "angle": 37.64,
-      "cameraId": "1"
-    },
-    {
-      "type": "image",
-      "width": 30,
-      "height": 19,
-      "left": 18.52,
-      "top": 362.26,
-      "angle": 331.41,
-      "cameraId": "2"
-    },
-    {
-      "type": "image",
-      "width": 30,
-      "height": 20,
-      "left": 339.99,
-      "top": 368.21,
-      "angle": 317.8,
-      "cameraId": "3"
-    }
-  ]
-}

+ 0 - 40
src/views/map-config/mini-map/MapBase/types.ts

@@ -1,40 +0,0 @@
-export interface MapData {
-  version: string;
-  backgroundImage: {
-    src: string;
-    type: string;
-    width: number;
-    height: number;
-    left: number;
-    top: number;
-    angle: number;
-  };
-  objects: CameraImgObject[];
-  defaultCameraId: string;
-}
-
-export interface CameraImgObject {
-  type: string;
-  width: number;
-  height: number;
-  left: number;
-  top: number;
-  angle: number;
-  cameraId: string;
-}
-
-export interface CameraImage extends fabric.Image {
-  cameraId?: string;
-}
-
-export type Canvas = fabric.Canvas;
-
-export const isCanvas = (canvas: Canvas | null | undefined): canvas is Canvas => {
-  return Boolean(canvas);
-};
-
-export type OnSelect = (image: CameraImage | null) => unknown;
-export type OnRightClick = (e: fabric.IEvent<MouseEvent>) => unknown;
-export type OnMoving = (e: fabric.IEvent<MouseEvent>) => unknown;
-export type OnRotating = (e: fabric.IEvent<MouseEvent>) => unknown;
-export type SetSelectedCamera = (e: CameraImage | null) => unknown;

+ 0 - 249
src/views/map-config/mini-map/MapBase/useCameraMap.ts

@@ -1,249 +0,0 @@
-import { fabric } from 'fabric';
-import { ref } from 'vue';
-import cameraImg from '@/assets/camera/camera.png';
-import {
-  CameraImage,
-  MapData,
-  OnMoving,
-  OnRightClick,
-  OnSelect,
-  isCanvas,
-  OnRotating,
-  SetSelectedCamera,
-} from './types';
-import { fabricSetting } from './fabricSetting';
-import { getRandomPosition } from './utils';
-
-fabricSetting();
-
-interface Props {
-  onSelect: OnSelect;
-  onRightClick: OnRightClick;
-  onMoving: OnMoving;
-  onRotating: OnRotating;
-  setSelectedCamera: SetSelectedCamera;
-  onObjectsAdded: () => unknown;
-}
-
-function useCameraMap(props: Props) {
-  let canvas;
-
-  const createMap = (canvasId: string) => {
-    canvas = new fabric.Canvas(canvasId, {
-      fireRightClick: true, // 启用右键,button的数字为3
-      stopContextMenu: true, // 禁止默认右键菜单
-    });
-    addListener();
-    // window.canvas = canvas;
-  };
-
-  /** 监听点击事件 */
-  const addListener = () => {
-    if (!isCanvas(canvas)) return;
-
-    canvas.on('mouse:down', (options) => {
-      if (!canvas) return;
-      const target = options.target as CameraImage;
-      const cameraId = target?.cameraId;
-      // console.log('当前选中的id是', cameraId);
-      console.log('mouse:down');
-      console.log(options);
-
-      // 判断:右键,且在元素上右键
-      // opt.button: 1-左键;2-中键;3-右键
-      // 在画布上点击:opt.target 为 null
-      if (options.button === 3 && options.target) {
-        props.onRightClick(options);
-        return;
-      }
-
-      if (!cameraId || !target) {
-        props.onSelect(null);
-        return;
-      }
-
-      props.onSelect(target);
-    });
-
-    canvas.on('object:moving', (e) => {
-      // console.log('object moving', e);
-      props.onMoving(e);
-    });
-    canvas.on('object:rotating', function (e) {
-      props.onRotating(e);
-    });
-
-    /** 监听点击选中的时候 */
-    canvas.on('selection:created', function (e) {
-      console.log('selection created', e);
-      props.setSelectedCamera((e.selected?.[0] as CameraImage) || null);
-    });
-    /** 监听有选中更新的时候 */
-    canvas.on('selection:updated', function (e) {
-      console.log('selection updated', e);
-      props.setSelectedCamera((e.selected?.[0] as CameraImage) || null);
-    });
-    /** 监听选中取消的时候 */
-    canvas.on('selection:cleared', function (e) {
-      console.log('selection cleared', e);
-      props.setSelectedCamera(null);
-    });
-    /** 监听object增加的时候 */
-    canvas.on('object:added', function (e) {
-      console.log('object add', e);
-      props.onObjectsAdded();
-    });
-    /** 监听object删除的时候 */
-    canvas.on('object:removed', function (e) {
-      console.log('object removed', e);
-      props.onObjectsAdded();
-    });
-  };
-
-  /**  上传背景图 */
-  const uploadBg = (imgUrl: string) => {
-    if (!isCanvas(canvas)) return;
-    fabric.Image.fromURL(imgUrl, (img) => {
-      const refImg = ref(img);
-      console.log('image', img);
-      canvas!.setWidth(img.width!);
-      canvas!.setHeight(img.height!);
-      refImg.value.lockScalingX = true;
-      refImg.value.lockScalingY = true;
-      // 设置背景图
-      canvas?.setBackgroundImage(refImg.value, canvas!.renderAll.bind(canvas));
-    });
-  };
-
-  /**  增加一个摄像头 */
-  const addCamera = (cameraId: string): Promise<CameraImage> => {
-    if (!isCanvas(canvas)) return Promise.reject();
-    // eslint-disable-next-line @typescript-eslint/no-this-alias
-    return new Promise((resolve) => {
-      fabric.Image.fromURL(cameraImg, (img) => {
-        const cImg = ref(img as unknown as CameraImage);
-        cImg.value.set({
-          left: getRandomPosition(),
-          top: getRandomPosition(),
-          cameraId,
-        });
-        cImg.value.lockScalingX = true;
-        cImg.value.lockScalingY = true;
-
-        canvas?.add(cImg.value);
-        canvas?.setActiveObject(cImg.value);
-        // cImg.value.on('moving', function (e) {
-        //   props.onMoving(e);
-        // });
-        // cImg.value.on('rotating', function (e) {
-        //   props.onRotating(e);
-        // });
-
-        resolve(cImg.value);
-      });
-    });
-  };
-
-  /**  删除一个摄像头 */
-  const removeActiveCamera = () => {
-    if (!isCanvas(canvas)) return;
-    const activeObject = canvas?.getActiveObject();
-    if (!activeObject) return;
-    canvas.remove(activeObject);
-  };
-
-  /** 导出JSON格式 */
-  const toJSON = () => {
-    if (!isCanvas(canvas)) return;
-    const initialJSON = canvas.toJSON(['cameraId']);
-    /** toJSON返回值的类型它写错了,应该是有backgroundImage的 */
-    const { src, type, version, width, height, left, top, angle } =
-      (initialJSON as any).backgroundImage || {};
-
-    const newObjects = initialJSON.objects.map((item) => {
-      return {
-        type: item.type,
-        width: item.width,
-        height: item.height,
-        left: item.left,
-        top: item.top,
-        angle: item.angle,
-        cameraId: (item as CameraImage).cameraId,
-      };
-    });
-    const newJson = {
-      version: initialJSON.version,
-      backgroundImage: { src, type, version, width, height, left, top, angle },
-      objects: newObjects,
-    };
-    return newJson;
-  };
-
-  /**  从json中加载 */
-  const loadFromJSON = (json: MapData): Promise<void> => {
-    if (!isCanvas(canvas)) return Promise.reject();
-    canvas.clear();
-    return new Promise((resolve) => {
-      const { width, height } = json.backgroundImage;
-      canvas?.setWidth(width);
-      canvas?.setHeight(height);
-      const objects = json.objects.map((item) => {
-        return {
-          ...item,
-          src: cameraImg,
-        };
-      });
-      canvas?.loadFromJSON({ ...json, objects }, () => {
-        resolve();
-      });
-    });
-  };
-
-  /**  更新摄像头的渲染 */
-  const renderCamera = () => {
-    canvas?.renderAll();
-  };
-  /**  */
-  const clear = () => {
-    canvas?.clear();
-  };
-
-  /**  是否已经存在这个cameraId */
-  const hasCamera = (cameraId: string) => {
-    const cameraIds =
-      canvas?.toJSON(['cameraId']).objects.map((item) => (item as CameraImage).cameraId) || [];
-    return cameraIds.includes(cameraId);
-  };
-
-  const getObjects = () => {
-    return canvas?.getObjects() as CameraImage[];
-  };
-  const getActiveObject = () => {
-    return canvas?.getActiveObject() as CameraImage;
-  };
-
-  /**  根据cameraId查找某个元素 */
-  const getCameraById = (cameraId: string) => {
-    return canvas
-      ?.getObjects()
-      .find((x) => (x as CameraImage).cameraId === cameraId) as CameraImage;
-  };
-
-  return {
-    canvas,
-    createMap,
-    uploadBg,
-    addCamera,
-    removeActiveCamera,
-    toJSON,
-    loadFromJSON,
-    renderCamera,
-    clear,
-    hasCamera,
-    getCameraById,
-    getObjects,
-    getActiveObject,
-  };
-}
-
-export default useCameraMap;

+ 0 - 22
src/views/map-config/mini-map/MapBase/utils.ts

@@ -1,22 +0,0 @@
-import { fabric } from 'fabric';
-export function getRandomPosition() {
-  return 100 + Math.floor(Math.random() * 30);
-}
-
-/** 根据camera位置得到fav icon的位置 */
-export function getFavPositionByCamera(target?: fabric.Object | null) {
-  if (!target || !target.oCoords) return null;
-  return { left: target.oCoords?.tr.x, top: target.oCoords?.tr.y };
-}
-
-export function createSelectedPositionHash(target) {
-  if (!target) return '';
-  return (
-    target.cameraId +
-    String(target.oCoords.tr.x) +
-    '_' +
-    String(target.oCoords.tr.y) +
-    '_' +
-    String(target.angle)
-  );
-}

+ 43 - 23
src/views/map-config/mini-map/MiniMapConfig.vue

@@ -17,7 +17,6 @@
           />
         </div>
         <div class="flex">
-          <!-- <el-button @click="mapEditor.toJson">tojson</el-button> -->
           <el-upload
             class="avatar-uploader flex justify-center items-center"
             action="/skyeye-admin-api/layout/uploadPicture"
@@ -64,7 +63,7 @@
             class="camera-item flex justify-start items-center"
             :class="{
               isAdded: isAddedCamera(item.code),
-              isActive: item.code === activeCameraId,
+              isActive: item.code === caremaActiveId,
             }"
             @click="handleAddCamera(item.code)"
           >
@@ -82,7 +81,13 @@
           </div>
         </el-scrollbar>
       </div>
-      <div ref="drawContainer" id="drawContainer" class="draw-container">
+      <div ref="drawContainer" class="draw-container">
+        <KonvaMap
+          ref="konvaMap"
+          @change-default-camera="changeDefault"
+          @send-camera-id="sendCameras"
+          v-moveable:1
+        />
         <div id="editContainer" v-moveable:1></div>
         <el-upload
           v-if="!hasBg"
@@ -109,17 +114,20 @@
   import { onMounted, ref } from 'vue';
   import { updateMinMapViewLayoutApi } from '@/api/scene/scene';
   import { computed } from 'vue';
-  import { Search, Refresh } from '@element-plus/icons-vue';
-  import useMapEditor from './hooks/useMapEditor';
+  import { Search, Refresh, ArrowLeft } from '@element-plus/icons-vue';
+  import KonvaMap from './MapBase/KonvaMap.vue';
 
-  const mapEditor = useMapEditor();
-  const { activeCameraId, addedCameras, bgImgUrl } = mapEditor;
   const miniMap = useMiniMap();
   const { scenesTree, shopCameraList, selectedShopCode, selectedShopDetail } = storeToRefs(miniMap);
   const { getScenesTree, getShowCameras, getMapLayout } = miniMap;
 
   const drawContainer = ref<HTMLDivElement>();
 
+  const konvaMap = ref();
+  const caremaActiveId = ref<string>('');
+  const camerasAdded = ref<string[]>([]);
+  const imgUrlBg = ref<string>('');
+
   const searchKey = ref('');
   // 是否已有背景图
   const hasBg = ref(false);
@@ -132,21 +140,32 @@
       return false;
     }
   };
+  const sendCameras = (camerasList) => {
+    camerasAdded.value = camerasList.map((item) => {
+      return item.id;
+    });
+    // console.log('camerasAdded.value', camerasAdded.value);
+  };
 
   /** 判断相机是否已经添加 */
   const isAddedCamera = (cameraId: string) => {
-    const index = addedCameras.value.findIndex((item) => item === cameraId);
+    const index = camerasAdded.value.findIndex((item) => item === cameraId);
     return index >= 0;
   };
 
+  const changeDefault = (defaultCameraId) => {
+    caremaActiveId.value = defaultCameraId;
+  };
+
   const handleAvatarSuccess = (e) => {
-    bgImgUrl.value = e.data;
-    mapEditor.addBg();
+    //自己的
+    imgUrlBg.value = e.data;
+    konvaMap.value.addBg(imgUrlBg.value);
     hasBg.value = true;
   };
 
   const changeShop = (code: string) => {
-    mapEditor.resetMap();
+    konvaMap.value.resetMap();
     getShopContent(code);
     hasBg.value = false;
   };
@@ -158,23 +177,22 @@
         return;
       }
       hasBg.value = true;
-      mapEditor.createMap(res);
+      konvaMap.value.createMap(res);
     });
   };
 
   const handleWholeClick = (e) => {
-    if (e.button === 0) {
-      mapEditor.destoryOptBlock();
-    }
+    // if (e.button === 0) {
+    //   mapEditor.destoryOptBlock();
+    // }
   };
 
   onMounted(() => {
     getScenesTree({ level: 2, valueKey: 'code', labelKey: 'name', disabled: true });
-    mapEditor.initContainer({
-      container: 'editContainer',
-      width: drawContainer.value!.clientWidth,
-      height: drawContainer.value!.clientHeight,
-    });
+    konvaMap.value.resizeContainer(
+      drawContainer.value!.clientWidth,
+      drawContainer.value!.clientHeight,
+    );
     if (selectedShopCode.value) {
       getShopContent(selectedShopCode.value);
     }
@@ -193,11 +211,14 @@
       });
       return;
     }
-    mapEditor.addCamera(cameraId);
+    konvaMap.value.addCamera(cameraId);
+    // mapEditor.addCamera(cameraId);
+    // konvaMap.value.addCamera(cameraId);
   };
 
   const handleSave = () => {
-    const layout = mapEditor.toJson();
+    // const layout = mapEditor.toJson();
+    const layout = konvaMap.value.saveLayout();
     updateMinMapViewLayoutApi({ layout, targetId: String(selectedShopDetail.value?.id) }).then(
       (res) => {
         console.log('updateMinMapViewLayoutApi', res);
@@ -315,4 +336,3 @@
     font-weight: 400;
   }
 </style>
-./MapBase/useCameraMap ./MapBase/CameraMapBak

+ 0 - 52
src/views/map-config/mini-map/components/CameraOptBar.vue

@@ -1,52 +0,0 @@
-<template>
-  <div class="opt-container">
-    <div class="opt-item" :class="{ disabled: props.disabled }" @click="setCamera">
-      <span>设为默认相机</span>
-    </div>
-  </div>
-</template>
-
-<script setup lang="ts">
-  const props = defineProps({
-    disabled: { type: Boolean, default: () => true },
-    onSetDefault: { type: Function },
-  });
-
-  const setCamera = () => {
-    if (!props.disabled) {
-      props.onSetDefault!();
-    }
-  };
-</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 {
-    background-color: #f1f2f5;
-    color: #bcbdc0;
-    cursor: not-allowed;
-  }
-</style>

+ 0 - 33
src/views/map-config/mini-map/components/EditDimension.vue

@@ -1,33 +0,0 @@
-<template>
-  <span style="padding-left: 10px">
-    <span>{{ props.label }}: </span>
-    <ElInput
-      style="width: 50px"
-      size="small"
-      @input="handleChange"
-      v-model="val"
-      :disabled="props.disabled"
-    />
-  </span>
-</template>
-<script lang="ts" setup>
-  import { ref, watch } from 'vue';
-  import { ElInput } from 'element-plus';
-  const props = defineProps<{ label: string; modelValue: number; disabled?: boolean }>();
-
-  const val = ref();
-  watch(
-    () => props.modelValue,
-    () => {
-      val.value = Math.floor(props.modelValue);
-    },
-    { immediate: true },
-  );
-
-  const emits = defineEmits<{ (e: 'update:modelValue', val: number): unknown }>();
-
-  const handleChange = (e) => {
-    emits('update:modelValue', Number(e));
-  };
-</script>
-<style scoped></style>

+ 0 - 52
src/views/map-config/mini-map/components/SelectedCameraToolbar.vue

@@ -1,52 +0,0 @@
-<template>
-  <div>
-    已选中相机<span>: {{ selectedCamera?.cameraId }}</span>
-    <span
-      ><EditDimension
-        v-model="selectedCamera.width"
-        label="width"
-        @update:model-value="renderMap"
-        disabled
-    /></span>
-    <span
-      ><EditDimension
-        v-model="selectedCamera.height"
-        label="height"
-        @update:model-value="renderMap"
-        disabled
-    /></span>
-    <span
-      ><EditDimension v-model="selectedCamera.left" label="left" @update:model-value="renderMap"
-    /></span>
-    <span
-      ><EditDimension v-model="selectedCamera.top" label="top" @update:model-value="renderMap"
-    /></span>
-    <span
-      ><EditDimension v-model="selectedCamera.angle" label="angle" @update:model-value="renderMap"
-    /></span>
-  </div>
-</template>
-<script lang="ts" setup>
-  import { ref } from 'vue';
-  import EditDimension from './EditDimension.vue';
-
-  const props = defineProps<{
-    selectedCamera: {
-      cameraId: string;
-      width: number;
-      height: number;
-      left: number;
-      top: number;
-      angle: number;
-    };
-  }>();
-
-  const selectedCamera = ref(props.selectedCamera);
-
-  const emits = defineEmits<{ (e: 'renderMap'): unknown }>();
-
-  const renderMap = () => {
-    emits('renderMap');
-  };
-</script>
-<style scoped></style>

+ 0 - 436
src/views/map-config/mini-map/hooks/useMapEditor.ts

@@ -1,436 +0,0 @@
-import { computed, h, onBeforeUnmount, onMounted, ref, render } from 'vue';
-import Konva from 'konva';
-import cameraImg from '@/assets/camera/cameraImg.png';
-import favoritesImg from '@/assets/camera/favorites.png';
-import OptBar from '../components/CameraOptBar.vue';
-import DefaultTip from '../components/DefaultTip.vue';
-import { TipPositionEnum } from '../type';
-import { ElMessage } from 'element-plus';
-import { useGlobSetting } from '@/hooks/setting';
-import urlJoin from 'url-join';
-import useMiniMap from '../use-mini-map';
-import { storeToRefs } from 'pinia';
-
-export function useMapEditor() {
-  const miniMap = useMiniMap();
-  const { shopCameraList } = storeToRefs(miniMap);
-
-  // let initWidth; // 默认宽度
-  // let initHeight; // 默认高度
-  let stage: Konva.Stage | null = null;
-  let layer: Konva.Layer | null = null;
-  let copyLayer: Konva.Layer | null = null;
-  let defaultIcon: Konva.Image | null = null; // 默认相机的图标shape
-  const addedCameras = ref<string[]>([]); // 已添加相机列表
-  const activeGroup = ref<Konva.Group | null>(null); // transformer激活的相机
-  const defaultCameraId = ref(''); // 默认相机的ID
-  let optBlock: HTMLDivElement | null = null; // 鼠标右击弹出的选项组
-  let defaultTip: HTMLDivElement | null = null; // 默认相机悬浮tip
-  let isTransform = false; // 是否再变换中
-  const activeCameraId = computed(() => activeGroup.value?.id()); // 当前选中相机ID
-  const bgImgUrl = ref<string>('');
-
-  const globSetting = useGlobSetting();
-
-  /** 容器初始化 */
-  const initContainer = (opt: Konva.StageConfig) => {
-    // initWidth = opt.width || 0;
-    // initHeight = opt.height || 0;
-    stage = new Konva.Stage(opt);
-    stage.on('click tap', handleStageClick);
-    window.stage = stage;
-    layer = new Konva.Layer();
-    copyLayer = new Konva.Layer();
-    stage.add(layer);
-    stage.add(copyLayer);
-    addDefaultIcon();
-  };
-
-  /** 初始生成默认相机的图标shape,但不可见 */
-  const addDefaultIcon = () => {
-    const favImg = new Image();
-    favImg.onload = () => {
-      defaultIcon = new Konva.Image({
-        x: 18,
-        y: -16,
-        width: 16,
-        height: 16,
-        image: favImg,
-        id: 'defaultIcon',
-        visible: false,
-        rotation: 0,
-      });
-      bindBaseEvt(defaultIcon);
-      layer?.add(defaultIcon);
-      layer?.batchDraw();
-    };
-    favImg.src = favoritesImg;
-  };
-
-  /** 更换背景图时根据图片大小重置容器宽高 */
-  const resizeContainer = (width, height) => {
-    // const newWidth = width > initWidth ? width : initWidth;
-    // const newHeight = height > initHeight ? height : initHeight;
-    // stage?.width(newWidth);
-    // stage?.height(newHeight);
-    stage?.width(width);
-    stage?.height(height);
-  };
-
-  /** 添加背景 */
-  const addBg = () => {
-    const imgUrl = urlJoin(globSetting.imgUrl!, bgImgUrl.value);
-    const bgNode = layer?.find('#bgImg')[0] as Konva.Image;
-    const bgImg = new Image();
-    bgImg.onload = () => {
-      // 判断是否已有背景
-      if (!bgNode) {
-        const mapBg = new Konva.Image({
-          x: 0,
-          y: 0,
-          image: bgImg,
-          width: bgImg.width,
-          height: bgImg.height,
-          id: 'bgImg',
-        });
-        layer?.add(mapBg);
-        mapBg.moveToBottom();
-      } else {
-        bgNode.width(bgImg.width);
-        bgNode.height(bgImg.height);
-        bgNode.image(bgImg);
-      }
-      resizeContainer(bgImg.width, bgImg.height);
-      layer?.batchDraw();
-    };
-    bgImg.src = imgUrl;
-  };
-
-  /** 变更需要激活transform的相机 */
-  const attachTransformer = (group: Konva.Group): Konva.Transformer => {
-    activeGroup.value?.draggable(false);
-    activeGroup.value = group;
-    group.draggable(true);
-    stage!.find('Transformer')[0]?.destroy(); // 清除现有transformer
-    const id = group.id();
-    const tr = new Konva.Transformer({
-      keepRatio: true,
-      rotateAnchorOffset: 30,
-      rotationSnaps: [0, 45, 90, 135, 180, 225, 270, 315],
-      enabledAnchors: ['top-left', 'top-right', 'bottom-left', 'bottom-right'],
-      id: 'tr_' + id,
-    });
-    tr.nodes([group]);
-    layer?.add(tr);
-    layer?.draw();
-
-    group.on('dragstart', handleDragStart);
-    group.on('dragstart', handleDragEnd);
-
-    return tr;
-  };
-
-  /** 添加相机 */
-  const addCamera = (id: string) => {
-    const group = new Konva.Group({
-      x: 50,
-      y: 50,
-      id,
-      draggable: true,
-      name: 'group',
-    });
-    const camImg = new Image();
-    camImg.onload = () => {
-      const cameraIcon = new Konva.Image({
-        width: 52,
-        height: 37,
-        image: camImg,
-        name: 'image',
-      });
-      group.add(cameraIcon);
-      layer?.add(group);
-      bindBaseEvt(cameraIcon);
-      const tr = attachTransformer(group); // 添加的相机默认激活transformer
-
-      addedCameras.value.push(id);
-      // 如果是唯一相机,设置为默认相机
-      if (addedCameras.value.length === 1) {
-        defaultIcon?.show();
-        setDefaultCamera(group, tr);
-        tr.forceUpdate();
-      }
-    };
-    camImg.src = cameraImg;
-  };
-
-  /** 变更默认相机 */
-  const setDefaultCamera = (node: Konva.Group, tr?: Konva.Transformer) => {
-    defaultIcon?.moveTo(node);
-    tr?.forceUpdate();
-    defaultCameraId.value = node.id();
-  };
-
-  /** 创建右键选项组 */
-  const createOptBlock = (node: Konva.Group, x: number, y: number) => {
-    const id = node.id();
-    optBlock = document.createElement('div') as HTMLDivElement;
-    optBlock.setAttribute('style', `position: absolute; left: ${x}px; top: ${y}px;`);
-    const optBar = h(OptBar, {
-      disabled: id === defaultCameraId.value,
-      onSetDefault: () => {
-        const tr = layer?.find(`#tr_${id}`)[0] as Konva.Transformer;
-        setDefaultCamera(node, tr);
-        destoryOptBlock();
-      },
-    });
-    render(optBar, optBlock);
-    const parentEl = document.getElementById('drawContainer') as HTMLDivElement;
-    parentEl.append(optBlock);
-  };
-
-  /** 删除右键选项组 */
-  const destoryOptBlock = () => {
-    optBlock?.remove();
-    optBlock = null;
-  };
-
-  /** 创建默认tip */
-  const createDefaultTip = (x: number, y: number, pos: TipPositionEnum) => {
-    if (isTransform) {
-      return;
-    }
-    defaultTip = document.createElement('div') as HTMLDivElement;
-    defaultTip.setAttribute('style', `position: absolute; left: ${x}px; top: ${y}px;`);
-    const tipInstance = h(DefaultTip, { position: pos });
-    render(tipInstance, defaultTip);
-    const parentEl = document.getElementById('drawContainer') as HTMLDivElement;
-    parentEl.append(defaultTip);
-  };
-
-  /** 删除默认tip */
-  const destoryDefaultTip = () => {
-    defaultTip?.remove();
-    defaultTip = null;
-  };
-
-  /** 删除相机 */
-  const deleteCamera = () => {
-    // 判断是否为默认相机,默认相机不允许删除
-    if (activeGroup.value?.id() === defaultCameraId.value) {
-      ElMessage.error({
-        message: '无法删除默认相机',
-      });
-      return;
-    }
-    const index = addedCameras.value.findIndex((item) => item === activeGroup.value?.id());
-    index >= 0 && addedCameras.value.splice(index, 1);
-    activeGroup.value?.destroy();
-    stage!.find('Transformer')[0]?.destroy();
-    layer?.draw();
-  };
-
-  /** 鼠标悬浮事件 */
-  const handleMouseOver = (e) => {
-    // 禁用浏览器默认鼠标事件
-    document.oncontextmenu = () => {
-      return false;
-    };
-    const group = e.target.parent;
-    // 如果悬浮的相机是默认相机,弹出默认tip
-    if (group.id() === defaultCameraId.value) {
-      let pos = TipPositionEnum.TOP;
-      const tipPosition = defaultIcon?.absolutePosition();
-      let x = Number(tipPosition?.x.toFixed(2)) || 0;
-      let y = Number(tipPosition?.y.toFixed(2)) || 0;
-      const angle = group.rotation() >= 0 ? group.rotation() : group.rotation() + 360;
-      if (angle >= 30) {
-        if (angle <= 150) {
-          pos = TipPositionEnum.RIGHT;
-          x += 26;
-          y -= 17;
-        } else if (angle <= 210) {
-          pos = TipPositionEnum.BOTTOM;
-          y += 26;
-          x -= 50;
-        } else {
-          pos = TipPositionEnum.LEFT;
-          x -= 121;
-          y -= 25;
-        }
-      } else {
-        y -= 61;
-        x -= 43;
-      }
-      createDefaultTip(x, y, pos);
-    }
-  };
-
-  /** 鼠标离开事件 */
-  const handleMouseLeave = () => {
-    // 恢复浏览器默认事件
-    document.oncontextmenu = () => {
-      return true;
-    };
-    defaultTip && destoryDefaultTip();
-  };
-
-  /** 开始拖拽事件 */
-  const handleDragStart = () => {
-    isTransform = true;
-    destoryDefaultTip();
-    destoryOptBlock();
-  };
-
-  /** 结束拖拽事件 */
-  const handleDragEnd = () => {
-    isTransform = false;
-  };
-
-  /** 全局点击事件 */
-  const handleStageClick = (e) => {
-    // 点击舞台取消现有激活的transformer
-    if (e.target === stage) {
-      stage!.find('Transformer')[0].destroy();
-      layer!.draw();
-      return;
-    }
-
-    // 判断点击对象是否为相机
-    if (!e.target.hasName('image')) {
-      return;
-    }
-    const parent = e.target.parent;
-    if (!parent.hasName('group')) {
-      return;
-    }
-    const group = e.target.parent;
-    attachTransformer(group);
-    // 判断是否为右键点击
-    if (e.evt.button === 2) {
-      createOptBlock(group, e.evt.offsetX + 20, e.evt.offsetY);
-    }
-  };
-
-  /** 键盘点击事件 */
-  const handleKeyDown = (e) => {
-    // 删除键
-    if (e.keyCode === 46 || e.code === 'Delete') {
-      deleteCamera();
-    }
-  };
-
-  // 基础监听事件绑定
-  const bindBaseEvt = (node: Konva.Node) => {
-    // node.on('transform', handleDragStart);
-    // node.on('transformend', handleDragEnd);
-    node.on('mouseover', handleMouseOver);
-    node.on('mouseleave', handleMouseLeave);
-  };
-
-  /** 输出布局json */
-  const toJson = () => {
-    const json = stage!.toJSON();
-    const cameras = JSON.parse(json)
-      .children[0].children.filter((node) => node.className === 'Group')
-      .map((item) => {
-        return {
-          cameraId: item.attrs.id,
-          rotation: Number((item.attrs.rotation | 0).toFixed(2)),
-          x: Math.round(item.attrs.x | 0),
-          y: Math.round(item.attrs.y | 0),
-          scaleX: Number((item.attrs.scaleX | 1).toFixed(1)),
-          scaleY: Number((item.attrs.scaleY | 1).toFixed(1)),
-          url: shopCameraList.value.find((cam) => cam.code === item.attrs.id)?.pushstreamIp || '',
-        };
-      });
-    const layout = {
-      bgInfo: {
-        bgImg: bgImgUrl.value,
-        width: layer?.find('#bgImg')[0].width(),
-        height: layer?.find('#bgImg')[0].height(),
-      },
-      defaultCameraId: defaultCameraId.value,
-      cameraList: cameras,
-    };
-
-    return JSON.stringify(layout);
-  };
-
-  /** 导入布局json */
-  const createMap = (layout) => {
-    // const layout = JSON.parse(json);
-    bgImgUrl.value = layout.bgInfo.bgImg;
-    addBg();
-    layout.cameraList.forEach((camera) => {
-      const group = new Konva.Group({
-        x: camera.x,
-        y: camera.y,
-        id: camera.cameraId,
-        rotation: camera.rotation,
-        scaleX: camera.scaleX,
-        scaleY: camera.scaleY,
-        draggable: false,
-        name: 'group',
-      });
-      const camImg = new Image();
-      camImg.onload = () => {
-        const cameraIcon = new Konva.Image({
-          width: 52,
-          height: 37,
-          image: camImg,
-          name: 'image',
-        });
-        group.add(cameraIcon);
-        layer?.add(group);
-        bindBaseEvt(cameraIcon);
-        addedCameras.value.push(camera.cameraId);
-
-        if (camera.cameraId === layout.defaultCameraId) {
-          setDefaultCamera(group);
-          defaultIcon?.show();
-        }
-
-        if (addedCameras.value.length === layout.cameraList.length) {
-          layer?.batchDraw();
-        }
-      };
-      camImg.src = cameraImg;
-    });
-  };
-
-  const resetMap = () => {
-    defaultIcon?.moveTo(copyLayer);
-    layer?.clear();
-    layer?.removeChildren();
-    defaultIcon?.moveTo(layer);
-    addedCameras.value = [];
-    activeGroup.value = null;
-    defaultCameraId.value = '';
-    isTransform = false;
-    bgImgUrl.value = '';
-  };
-
-  onMounted(() => {
-    window.addEventListener('keydown', handleKeyDown);
-  });
-
-  onBeforeUnmount(() => {
-    window.removeEventListener('keydown', handleKeyDown);
-  });
-
-  return {
-    defaultCameraId,
-    activeCameraId,
-    addedCameras,
-    bgImgUrl,
-    initContainer,
-    addBg,
-    addCamera,
-    destoryOptBlock,
-    toJson,
-    createMap,
-    resetMap,
-  };
-}
-
-export default useMapEditor;

+ 21 - 0
src/views/map-config/mini-map/type.ts

@@ -4,3 +4,24 @@ export enum TipPositionEnum {
   BOTTOM = 'bottom',
   LEFT = 'left',
 }
+
+export interface camerasImgType {
+  x?: number;
+  y?: number;
+  width?: number;
+  height?: number;
+  draggable?: boolean;
+  rotation?: number;
+  scaleX?: number;
+  scaleY?: number;
+  image?: HTMLImageElement;
+  name?: string;
+  url?: string;
+}
+
+export interface camerasGroupType {
+  id?: string;
+  groupConfig: camerasImgType;
+  config: camerasImgType;
+  isDefault?: boolean;
+}