sunhongyao341504 2 лет назад
Родитель
Сommit
4342d1c0a9

+ 3 - 0
src/main.ts

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

+ 490 - 72
src/views/page-config/ConfigEdit.vue

@@ -1,87 +1,505 @@
 <template>
 <template>
-  <div>
-    <div ref="mapRef" id="mapRef" class="test-icon" style="display: flex; color: red">
-      <!-- <SvgIcon ref="mapRef" icon-name="posPoint" color="red" class="test-icon" /> -->
-      <!-- <img :src="testIcon" color="red" class="test-icon" /> -->
-      <svg width="26.762" fill="currentColor" height="32.282" xmlns="http://www.w3.org/2000/svg">
-        <defs>
-          <linearGradient x1="84.362%" y1="100%" x2="15.638%" y2="0%" id="prefix__a">
-            <stop stop-color="currentColor" offset="0%" />
-            <stop stop-color="currentColor" stop-opacity=".5" offset="100%" />
-          </linearGradient>
-          <!-- <linearGradient x1="50%" y1="0%" x2="50%" y2="98.062%" id="prefix__b">
-            <stop stop-color="#D3FFFA" offset="0%" />
-            <stop stop-color="#C1EDFF" offset="51.021%" />
-            <stop stop-color="#90E0FF" offset="100%" />
-        </linearGradient> -->
-        </defs>
-        <g fill="none" fill-rule="evenodd">
-          <path
-            d="M13.38.25c3.361 0 6.722 1.281 9.286 3.844a13.101 13.101 0 013.846 9.279c0 3.36-1.287 6.585-3.586 9.012l-.26.266-9.285 9.278-9.286-9.28A13.101 13.101 0 01.25 13.372c0-3.484 1.384-6.824 3.845-9.278A13.094 13.094 0 0113.381.25zm0 8.916a4.24 4.24 0 00-3.015 1.256 4.282 4.282 0 00-1.249 3.03 4.28 4.28 0 001.249 3.029 4.24 4.24 0 003.016 1.255 4.24 4.24 0 003.016-1.255 4.282 4.282 0 001.248-3.03 4.282 4.282 0 00-1.248-3.03 4.24 4.24 0 00-3.016-1.255z"
-            stroke-opacity=".498"
-            stroke="#B0E8FF"
-            stroke-width=".5"
-            fill-opacity=".9"
-            fill="currentColor"
-            xlink:href="url(#prefix__a)"
-          />
-          <ellipse
-            fill="#FFFFFF"
-            fill-rule="nonzero"
-            cx="13.634"
-            cy="13.619"
-            rx="4.545"
-            ry="4.54"
+  <div class="page">
+    <div class="page-head flex items-center">
+      <el-icon size="20"><ArrowLeft /></el-icon>
+      <div class="head-opt flex-1 flex justify-between items-center">
+        <div>
+          <span>场景:</span>
+          <el-select
+            v-model="selectedCompany"
+            class="m-2"
+            placeholder="请选择相关公司"
+            style="width: 216px"
+            @change="changeCompany"
+          >
+            <el-option
+              v-for="item in scenesInfos"
+              :key="item.id"
+              :label="item.name"
+              :value="item.id"
+            />
+          </el-select>
+        </div>
+        <div v-if="selectedCompany" style="display: flex; margin-top: 8px">
+          <div class="label-workshop">选择标签:</div>
+          <div>
+            <el-radio-group
+              v-model="label"
+              :border="true"
+              style="display: flex"
+              @change="changeShop"
+            >
+              <el-radio-button
+                v-for="item in labelList"
+                :key="item.id"
+                :label="item.id!"
+                class="label-select"
+                >{{ item.name }}</el-radio-button
+              >
+            </el-radio-group></div
+          >
+        </div>
+        <div class="flex">
+          <!-- <el-button @click="toJson">tojson</el-button> -->
+          <el-upload
+            class="avatar-uploader flex justify-center items-center"
+            action="/skyeye-admin-api/homepageConfig/updateCompanyPicture"
+            :show-file-list="false"
+            :on-success="handleAvatarSuccess"
+            :with-credentials="true"
+            name="file"
+            :data="{ companyId: selectedCompany, labelId: label, deleteFileName: bgImg }"
+          >
+            <el-button style="font-size: 12px" :icon="Refresh" :disabled="!hasBg">
+              替换照片
+            </el-button>
+          </el-upload>
+
+          <el-button
+            @click="handleSave"
+            style="margin-left: 40px"
+            type="primary"
+            :disabled="!selectedCompany && !label"
+            >保存为布局
+          </el-button>
+        </div>
+      </div>
+    </div>
+    <div class="paint-tool flex">
+      <div class="camera-list">
+        <div>
+          <span class="label-text flex">车间列表:</span>
+          <ElInput
+            class="search-put"
+            style="margin: 10px 0; width: 230px"
+            placeholder="请输入搜索内容"
+            v-model="searchKey"
+            :suffix-icon="Search"
           />
           />
-        </g>
-      </svg>
+        </div>
+        <span v-if="filterShopList.length == 0" class="ml-1" style="color: #3f3f3f">
+          提示:请先选择相应公司和图片
+        </span>
+        <el-scrollbar v-else style="position: relative; height: calc(100% - 90px)">
+          <div
+            v-for="item in filterShopList"
+            :key="item.code"
+            class="camera-item flex justify-start items-center"
+            :class="{
+              isAdded: isAddedShop(item.id),
+              isActive: item.id === activeShop.id,
+            }"
+            @click="handleAddShop(item)"
+          >
+            <span class="camera-id">{{ item.name }}</span>
+          </div>
+        </el-scrollbar>
+      </div>
+      <div ref="drawContainer" id="drawContainer" class="draw-container">
+        <div id="shopEditContainer" v-moveable:1>
+          <MapContainer />
+        </div>
+        <el-upload
+          v-if="!hasBg"
+          class="upload-icon flex justify-center items-center"
+          action="/skyeye-admin-api/homepageConfig/uploadCompanyPicture"
+          :show-file-list="false"
+          :before-upload="handleBeforeUpload"
+          :on-success="handleAvatarSuccess"
+          :with-credentials="true"
+          name="file"
+          :data="{ companyId: selectedCompany, labelId: label }"
+        >
+          <img src="~@/assets/images/img-upload.png" />
+        </el-upload>
+      </div>
     </div>
     </div>
-    <img class="test-icon" :src="img" />
+    <el-tooltip
+      class="box-item position-tooltip"
+      effect="dark"
+      content="显示侧边栏"
+      :offset="12"
+      placement="left"
+    >
+      <div
+        v-if="leftShow"
+        class="circle-rectangle"
+        :class="{ 'shape-shadow': shadow }"
+        @mouseover="shadowAdd"
+        @mouseout="shadowRemove"
+        @click="dialogReopen"
+      >
+        <el-icon class="left-icon" size="16px"><ArrowLeftBold /></el-icon>
+        <el-icon style="margin-top: 7px" size="16px"><ArrowLeftBold /></el-icon>
+      </div>
+    </el-tooltip>
+
+    <ConfigDialog ref="configDrawer" @on-close="onClose" class="drawer-position" />
+
+    <ConfigFinish
+      :visible="visibleResult"
+      :status="configStatus"
+      @on-close="closeResult"
+      class="feedback-position"
+    />
   </div>
   </div>
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-  import { onMounted, ref } from 'vue';
-  import { SvgIcon } from '@/components/SvgIcon';
-  import html2canvas from 'html2canvas';
-  import { Canvg } from 'canvg';
-  import testIcon from '@/assets/1.svg';
+  import ConfigDialog from './component/ConfigDrawer.vue';
+  import ConfigFinish from './component/ConfigFinish.vue';
+  import { storeToRefs } from 'pinia';
+  import { ElMessage, ElInput } from 'element-plus';
+  import { onBeforeUnmount, onMounted, ref } from 'vue';
+  import { WorkShopInfoItem } from '@/api/scene/scene';
+  import { computed } from 'vue';
+  import { Search, Refresh, ArrowLeftBold, ArrowLeft } from '@element-plus/icons-vue';
+  import usePageConfig from './usePageConfig';
+  import MapContainer from './component/mapContainer/MapContainer.vue';
+  import useMapEditor, { LabelPositionEnum } from './stores/useMapEditor';
+  import { uploadCompanyLayout, updateCompanyLayout, getCompanyLayoutApi } from '@/api/scene/scene';
+  import safeParse from '@/utils/safeParse';
+  import { useRouter } from 'vue-router';
+
+  const mapEditor = useMapEditor();
+  const { bgImg, addedShops, activeShop } = storeToRefs(mapEditor);
+  const { addShop, addBg, toJson, resetMap, createMap, deleteShop } = mapEditor;
+
+  const router = useRouter();
+
+  const pageConfig = usePageConfig();
+  const { selectedCompany, scenesInfos, label, labelList, shopList, layoutId } = pageConfig;
+
+  const drawContainer = ref<HTMLDivElement>();
+
+  const searchKey = ref('');
+  // 是否已有背景图
+  const hasBg = ref(false);
+
+  const handleBeforeUpload = () => {
+    if (!selectedCompany.value || !label.value) {
+      ElMessage.error({
+        message: '请先选择公司和标签',
+      });
+      return false;
+    }
+  };
+
+  /** 判断相机是否已经添加 */
+  const isAddedShop = (shopId: number) => {
+    const index = addedShops.value.findIndex((item) => item.id === shopId);
+    return index >= 0;
+  };
+
+  //左边的浮动按钮
+  const leftShow = ref(true);
+  const onClose = (val) => {
+    leftShow.value = val;
+  };
+
+  const shadow = ref(false);
+  const shadowAdd = () => {
+    shadow.value = true;
+  };
+  const shadowRemove = () => {
+    shadow.value = false;
+  };
+
+  const configDrawer = ref();
+  const dialogReopen = () => {
+    configDrawer.value.openDialog();
+  };
+
+  //总体的保存,将整个数据传过去
+  const visibleResult = ref(false);
+  const configStatus = ref(true);
+  const closeResult = () => {
+    visibleResult.value = false;
+  };
+  /** ------------- */
+
+  const handleAvatarSuccess = (e) => {
+    bgImg.value = e.data;
+    addBg();
+    hasBg.value = true;
+  };
+
+  const changeShop = () => {
+    // resetMap();
+    getShopContent();
+    // hasBg.value = false;
+  };
+
+  const getShopContent = () => {
+    getCompanyLayoutApi({ companyId: selectedCompany.value || 2, labelId: label.value || 1 }).then(
+      (res) => {
+        if (!res) {
+          return;
+        }
+        layoutId.value = res.id;
+        const layoutJSON = res.layout ? safeParse(res.layout) : null;
+        if (!layoutJSON) {
+          return;
+        }
+        hasBg.value = true;
+        createMap(layoutJSON);
+      },
+    );
+  };
 
 
-  const mapRef = ref<HTMLDivElement>();
-  const img = ref('');
+  const changeCompany = () => {
+    label.value = undefined;
+    // resetMap();
+    // hasBg.value = false;
+  };
+
+  const handleKeyDown = (e) => {
+    // 删除键
+    if (e.keyCode === 46 || e.code === 'Delete') {
+      deleteShop();
+    }
+  };
+
+  onMounted(() => {
+    const routerParams = router.currentRoute.value.query;
+    if (routerParams.companyId) {
+      selectedCompany.value = Number(routerParams.companyId);
+      console.log(selectedCompany.value);
+    }
+
+    window.addEventListener('keydown', handleKeyDown);
+
+    if (selectedCompany.value && label.value) {
+      getShopContent();
+    }
+  });
+
+  const filterShopList = computed(() => {
+    const k = searchKey.value.trim();
+    if (!k) return shopList.value;
+    return shopList.value?.filter((x) => x.name?.includes(k));
+  });
+
+  const handleAddShop = (shop: WorkShopInfoItem) => {
+    if (!hasBg.value) {
+      ElMessage.warning({
+        message: '请先添加背景图片',
+      });
+      return;
+    }
+    const shopNode = {
+      ...shop,
+      x: 20,
+      y: 20,
+      scale: 1,
+      bgColor: 'rgba(58, 170, 209, 1)',
+      fontSize: 14,
+      fontColor: '#ffffff',
+      posType: LabelPositionEnum.LEFT,
+    };
+    addShop(shopNode);
+    dialogReopen();
+  };
+
+  const handleSave = () => {
+    const layout = toJson();
+    const param = {
+      layout,
+      targetId: selectedCompany.value || 2,
+      labelId: label.value || 1,
+    };
+    if (!layoutId.value) {
+      uploadCompanyLayout(param).then((res) => {
+        console.log('uploadCompanyLayout', res);
+        layoutId.value = res;
+        ElMessage.success('保存成功');
+      });
+    } else {
+      updateCompanyLayout({ ...param, id: layoutId.value }).then((res) => {
+        console.log('updateCompanyLayout', res);
+        layoutId.value = res;
+        ElMessage.success('更新成功');
+      });
+    }
+  };
 
 
   onMounted(() => {
   onMounted(() => {
-    // const svgNodes = [];
-    // const svgElements = document.body.querySelectorAll('#mapRef use');
-    // const svgElement = svgElements[0];
-    // console.log('svgElements==========', svgElement.outerHTML);
-    //   svgElements.forEach((el) => {
-    //     const childrens = el.childNodes;
-    //     childrens.forEach((item) => ((item as HTMLElement).style.opacity = '1'));
-    //     const newSvg = el.outerHTML.trim();
-    //     const newCanvas = document.createElement('canvas');
-    //     const ctx = newCanvas.getContext('2d') as CanvasRenderingContext2D;
-    //     newCanvas.width = el.getBoundingClientRect().width;
-    //     newCanvas.height = el.getBoundingClientRect().height;
-    //     console.log('new canvas===', newCanvas);
-    //     Canvg.fromString(ctx, newSvg);
-    //     mapRef.value?.appendChild(newCanvas);
-    //   });
-    html2canvas(mapRef.value!, {
-      width: 50,
-      height: 50,
-      useCORS: true,
-      allowTaint: true,
-      backgroundColor: 'transparent',
-    }).then((canvas) => {
-      img.value = canvas.toDataURL('images/jpg');
-    });
+    addBg();
+    hasBg.value = true;
+  });
+
+  onBeforeUnmount(() => {
+    window.removeEventListener('keydown', handleKeyDown);
+    resetMap();
   });
   });
 </script>
 </script>
 
 
-<style scoped>
-  .test-icon {
-    /* width: 50px;
-    height: 50px; */
+<style scoped lang="scss">
+  .page {
+  }
+
+  .page-head {
+    height: 54px;
+    padding-left: 15px;
+    background-color: #ffffff;
+  }
+  .head-opt {
+    margin-left: 20px;
+    padding-right: 15px;
+    font-size: 14px;
+    color: #3f3f3f;
+  }
+
+  .avatar-uploader {
+    border-radius: 4px;
+    margin-left: 30px;
+  }
+
+  .label-workshop {
+    font-size: 14px;
+    font-weight: 400;
+    margin-left: 36px;
+    margin-top: 6px;
+    margin-right: 16px;
+  }
+
+  .upload-icon {
+    position: absolute;
+    top: 0;
+    right: 0;
+    left: 0;
+    bottom: 0;
+    margin: auto;
+  }
+
+  .paint-tool {
+    position: relative;
+    height: calc(100vh - 138px);
+    margin-top: 2px;
+  }
+
+  .camera-list {
+    width: 250px;
+    padding: 0 10px;
+    background-color: #ffffff;
+  }
+  .label-text {
+    font-size: 14px;
+    font-weight: 600;
+    margin: 10px 0 5px 10px;
+  }
+  .camera-item {
+    height: 32px;
+    font-size: 14px;
+    padding-left: 8px;
+    font-weight: 400;
+    color: #404040;
+    line-height: 14px;
+    cursor: pointer;
+
+    &:hover {
+      background-color: #e6f7ff;
+      color: #1890ff;
+    }
+  }
+  .isAdded {
+    color: #1890ff;
+    cursor: not-allowed;
+  }
+  .isActive {
+    background-color: #e6f7ff;
+    color: #1890ff;
+  }
+  .camera-item-disabled {
+    color: #c6c6c6;
+  }
+  .camera-id {
+    width: 110px;
+  }
+  .space-name {
+    width: 120px;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+  }
+
+  .draw-container {
+    position: relative;
+    width: calc(100% - 300px);
+    margin: 20px;
+    overflow: hidden;
+  }
+
+  .drawer-position {
+    position: absolute;
+    right: 0px;
+    // left: 1150px;
+    top: 74px;
+    z-index: 99;
+    opacity: 1;
+    background: #ffffff;
+  }
+
+  .dialog-btn {
+    position: absolute;
+    right: 0px;
+    top: 66px;
+  }
+
+  .position-tooltip {
+    margin-right: -10px;
+  }
+
+  .circle-rectangle {
+    width: 40px;
+    height: 30px;
+    border-radius: 50% 0% 0% 50%;
+    position: absolute;
+    right: 0px;
+    top: 66px;
+    background-color: white;
+    display: flex;
+  }
+
+  .shape-shadow {
+    filter: drop-shadow(5px 5px 10px rgb(102, 100, 100));
+  }
+  .left-icon {
+    margin-top: 7px;
+    margin-left: 8px;
+    margin-right: -7px;
+  }
+
+  .feedback-position {
+    position: absolute;
+    left: 460px;
+    top: 150px;
+    z-index: 9999;
+  }
+
+  :deep(.search-put .el-input__wrapper) {
+    background-color: #f0f2f5;
+  }
+  :deep(.el-popper__arrow) {
+    display: none;
+  }
+  :deep(.el-tree-node__content:hover) {
+    background: #e6f4ff;
+  }
+  :deep(.el-button--primary) {
+    --el-button-disabled-bg-color: #bfbfbf;
+  }
+  :deep(.el-popover) {
+    width: unset !important;
+    min-width: 110px;
+    text-align: center;
+    font-weight: 400;
+  }
+  ::v-deep.el-radio-button {
+    margin-right: 8px;
+    box-shadow: none;
+    border-radius: 4px !important;
+    border: 1px solid #d9d9d9 !important;
   }
   }
 </style>
 </style>
+./MapBase/useCameraMap ./MapBase/CameraMapBak ./usePageConfig1

+ 0 - 500
src/views/page-config/ConfigEdit.vue.bak

@@ -1,500 +0,0 @@
-<template>
-  <div class="page">
-    <div class="page-head flex items-center">
-      <el-icon size="20"><ArrowLeft /></el-icon>
-      <div class="head-opt flex-1 flex justify-between items-center">
-        <div>
-          <span>场景:</span>
-          <el-select
-            v-model="selectedCompany"
-            class="m-2"
-            placeholder="请选择相关公司"
-            style="width: 216px"
-            @change="changeCompany"
-          >
-            <el-option
-              v-for="item in scenesInfos"
-              :key="item.id"
-              :label="item.name"
-              :value="item.id"
-            />
-          </el-select>
-        </div>
-        <div v-if="selectedCompany" style="display: flex; margin-top: 8px">
-          <div class="label-workshop">选择标签:</div>
-          <div>
-            <el-radio-group
-              v-model="label"
-              :border="true"
-              style="display: flex"
-              @change="changeShop"
-            >
-              <el-radio-button
-                v-for="item in labelList"
-                :key="item.id"
-                :label="item.id!"
-                class="label-select"
-                >{{ item.name }}</el-radio-button
-              >
-            </el-radio-group></div
-          >
-        </div>
-        <div class="flex">
-          <!-- <el-button @click="toJson">tojson</el-button> -->
-          <el-upload
-            class="avatar-uploader flex justify-center items-center"
-            action="/skyeye-admin-api/homepageConfig/updateCompanyPicture"
-            :show-file-list="false"
-            :on-success="handleAvatarSuccess"
-            :with-credentials="true"
-            name="file"
-            :data="{ companyId: selectedCompany, labelId: label, deleteFileName: bgImg }"
-          >
-            <el-button style="font-size: 12px" :icon="Refresh" :disabled="!hasBg">
-              替换照片
-            </el-button>
-          </el-upload>
-
-          <el-button
-            @click="handleSave"
-            style="margin-left: 40px"
-            type="primary"
-            :disabled="!selectedCompany && !label"
-            >保存为布局
-          </el-button>
-        </div>
-      </div>
-    </div>
-    <div class="paint-tool flex">
-      <div class="camera-list">
-        <div>
-          <span class="label-text flex">车间列表:</span>
-          <ElInput
-            class="search-put"
-            style="margin: 10px 0; width: 230px"
-            placeholder="请输入搜索内容"
-            v-model="searchKey"
-            :suffix-icon="Search"
-          />
-        </div>
-        <span v-if="filterShopList.length == 0" class="ml-1" style="color: #3f3f3f">
-          提示:请先选择相应公司和图片
-        </span>
-        <el-scrollbar v-else style="position: relative; height: calc(100% - 90px)">
-          <div
-            v-for="item in filterShopList"
-            :key="item.code"
-            class="camera-item flex justify-start items-center"
-            :class="{
-              isAdded: isAddedShop(item.id),
-              isActive: item.id === activeShop.id,
-            }"
-            @click="handleAddShop(item)"
-          >
-            <span class="camera-id">{{ item.name }}</span>
-          </div>
-        </el-scrollbar>
-      </div>
-      <div ref="drawContainer" id="drawContainer" class="draw-container">
-        <div id="shopEditContainer" v-moveable:1>
-          <MapContainer />
-        </div>
-        <el-upload
-          v-if="!hasBg"
-          class="upload-icon flex justify-center items-center"
-          action="/skyeye-admin-api/homepageConfig/uploadCompanyPicture"
-          :show-file-list="false"
-          :before-upload="handleBeforeUpload"
-          :on-success="handleAvatarSuccess"
-          :with-credentials="true"
-          name="file"
-          :data="{ companyId: selectedCompany, labelId: label }"
-        >
-          <img src="~@/assets/images/img-upload.png" />
-        </el-upload>
-      </div>
-    </div>
-    <el-tooltip
-      class="box-item position-tooltip"
-      effect="dark"
-      content="显示侧边栏"
-      :offset="12"
-      placement="left"
-    >
-      <div
-        v-if="leftShow"
-        class="circle-rectangle"
-        :class="{ 'shape-shadow': shadow }"
-        @mouseover="shadowAdd"
-        @mouseout="shadowRemove"
-        @click="dialogReopen"
-      >
-        <el-icon class="left-icon" size="16px"><ArrowLeftBold /></el-icon>
-        <el-icon style="margin-top: 7px" size="16px"><ArrowLeftBold /></el-icon>
-      </div>
-    </el-tooltip>
-
-    <ConfigDialog ref="configDrawer" @on-close="onClose" class="drawer-position" />
-
-    <ConfigFinish
-      :visible="visibleResult"
-      :status="configStatus"
-      @on-close="closeResult"
-      class="feedback-position"
-    />
-  </div>
-</template>
-
-<script setup lang="ts">
-  import ConfigDialog from './component/ConfigDrawer.vue';
-  import ConfigFinish from './component/ConfigFinish.vue';
-  import { storeToRefs } from 'pinia';
-  import { ElMessage, ElInput } from 'element-plus';
-  import { onBeforeUnmount, onMounted, ref } from 'vue';
-  import { WorkShopInfoItem } from '@/api/scene/scene';
-  import { computed } from 'vue';
-  import { Search, Refresh } from '@element-plus/icons-vue';
-  import usePageConfig from './usePageConfig';
-  import MapContainer from './component/mapContainer/MapContainer.vue';
-  import useMapEditor, { LabelPositionEnum } from './stores/useMapEditor';
-  import { uploadCompanyLayout, updateCompanyLayout, getCompanyLayoutApi } from '@/api/scene/scene';
-  import safeParse from '@/utils/safeParse';
-  import { useRouter } from 'vue-router';
-
-  const mapEditor = useMapEditor();
-  const { bgImg, addedShops, activeShop } = storeToRefs(mapEditor);
-  const { addShop, addBg, toJson, resetMap, createMap, deleteShop } = mapEditor;
-
-  const router = useRouter();
-
-  const pageConfig = usePageConfig();
-  const { selectedCompany, scenesInfos, label, labelList, shopList, layoutId } = pageConfig;
-
-  const drawContainer = ref<HTMLDivElement>();
-
-  const searchKey = ref('');
-  // 是否已有背景图
-  const hasBg = ref(false);
-
-  const handleBeforeUpload = () => {
-    if (!selectedCompany.value || !label.value) {
-      ElMessage.error({
-        message: '请先选择公司和标签',
-      });
-      return false;
-    }
-  };
-
-  /** 判断相机是否已经添加 */
-  const isAddedShop = (shopId: number) => {
-    const index = addedShops.value.findIndex((item) => item.id === shopId);
-    return index >= 0;
-  };
-
-  //左边的浮动按钮
-  const leftShow = ref(true);
-  const onClose = (val) => {
-    leftShow.value = val;
-  };
-
-  const shadow = ref(false);
-  const shadowAdd = () => {
-    shadow.value = true;
-  };
-  const shadowRemove = () => {
-    shadow.value = false;
-  };
-
-  const configDrawer = ref();
-  const dialogReopen = () => {
-    configDrawer.value.openDialog();
-  };
-
-  //总体的保存,将整个数据传过去
-  const visibleResult = ref(false);
-  const configStatus = ref(true);
-  const closeResult = () => {
-    visibleResult.value = false;
-  };
-  /** ------------- */
-
-  const handleAvatarSuccess = (e) => {
-    bgImg.value = e.data;
-    addBg();
-    hasBg.value = true;
-  };
-
-  const changeShop = () => {
-    resetMap();
-    getShopContent();
-    hasBg.value = false;
-  };
-
-  const getShopContent = () => {
-    getCompanyLayoutApi({ companyId: selectedCompany.value || 2, labelId: label.value || 1 }).then(
-      (res) => {
-        if (!res) {
-          return;
-        }
-        layoutId.value = res.id;
-        const layoutJSON = res.layout ? safeParse(res.layout) : null;
-        if (!layoutJSON) {
-          return;
-        }
-        hasBg.value = true;
-        createMap(layoutJSON);
-      },
-    );
-  };
-
-  const changeCompany = () => {
-    label.value = undefined;
-    resetMap();
-    hasBg.value = false;
-  };
-
-  const handleKeyDown = (e) => {
-    // 删除键
-    if (e.keyCode === 46 || e.code === 'Delete') {
-      deleteShop();
-    }
-  };
-
-  onMounted(() => {
-    const routerParams = router.currentRoute.value.query;
-    if (routerParams.companyId) {
-      selectedCompany.value = Number(routerParams.companyId);
-      console.log(selectedCompany.value);
-    }
-
-    window.addEventListener('keydown', handleKeyDown);
-
-    if (selectedCompany.value && label.value) {
-      getShopContent();
-    }
-  });
-
-  const filterShopList = computed(() => {
-    const k = searchKey.value.trim();
-    if (!k) return shopList.value;
-    return shopList.value?.filter((x) => x.name?.includes(k));
-  });
-
-  const handleAddShop = (shop: WorkShopInfoItem) => {
-    if (!hasBg.value) {
-      ElMessage.warning({
-        message: '请先添加背景图片',
-      });
-      return;
-    }
-    const shopNode = {
-      ...shop,
-      x: 20,
-      y: 20,
-      scale: 1,
-      bgColor: 'rgba(58, 170, 209, 1)',
-      fontSize: 14,
-      fontColor: '#ffffff',
-      posType: LabelPositionEnum.LEFT,
-    };
-    addShop(shopNode);
-    dialogReopen();
-  };
-
-  const handleSave = () => {
-    const layout = toJson();
-    const param = {
-      layout,
-      targetId: selectedCompany.value || 2,
-      labelId: label.value || 1,
-    };
-    if (!layoutId.value) {
-      uploadCompanyLayout(param).then((res) => {
-        console.log('uploadCompanyLayout', res);
-        layoutId.value = res;
-        ElMessage.success('保存成功');
-      });
-    } else {
-      updateCompanyLayout({ ...param, id: layoutId.value }).then((res) => {
-        console.log('updateCompanyLayout', res);
-        layoutId.value = res;
-        ElMessage.success('更新成功');
-      });
-    }
-  };
-
-  onBeforeUnmount(() => {
-    window.removeEventListener('keydown', handleKeyDown);
-    resetMap();
-  });
-</script>
-
-<style scoped lang="scss">
-  .page {
-  }
-
-  .page-head {
-    height: 54px;
-    padding-left: 15px;
-    background-color: #ffffff;
-  }
-  .head-opt {
-    margin-left: 20px;
-    padding-right: 15px;
-    font-size: 14px;
-    color: #3f3f3f;
-  }
-
-  .avatar-uploader {
-    border-radius: 4px;
-    margin-left: 30px;
-  }
-
-  .label-workshop {
-    font-size: 14px;
-    font-weight: 400;
-    margin-left: 36px;
-    margin-top: 6px;
-    margin-right: 16px;
-  }
-
-  .upload-icon {
-    position: absolute;
-    top: 0;
-    right: 0;
-    left: 0;
-    bottom: 0;
-    margin: auto;
-  }
-
-  .paint-tool {
-    position: relative;
-    height: calc(100vh - 138px);
-    margin-top: 2px;
-  }
-
-  .camera-list {
-    width: 250px;
-    padding: 0 10px;
-    background-color: #ffffff;
-  }
-  .label-text {
-    font-size: 14px;
-    font-weight: 600;
-    margin: 10px 0 5px 10px;
-  }
-  .camera-item {
-    height: 32px;
-    font-size: 14px;
-    padding-left: 8px;
-    font-weight: 400;
-    color: #404040;
-    line-height: 14px;
-    cursor: pointer;
-
-    &:hover {
-      background-color: #e6f7ff;
-      color: #1890ff;
-    }
-  }
-  .isAdded {
-    color: #1890ff;
-    cursor: not-allowed;
-  }
-  .isActive {
-    background-color: #e6f7ff;
-    color: #1890ff;
-  }
-  .camera-item-disabled {
-    color: #c6c6c6;
-  }
-  .camera-id {
-    width: 110px;
-  }
-  .space-name {
-    width: 120px;
-    white-space: nowrap;
-    overflow: hidden;
-    text-overflow: ellipsis;
-  }
-
-  .draw-container {
-    position: relative;
-    width: calc(100% - 300px);
-    margin: 20px;
-    overflow: hidden;
-  }
-
-  .drawer-position {
-    position: absolute;
-    right: 0px;
-    // left: 1150px;
-    top: 74px;
-    z-index: 99;
-    opacity: 1;
-    background: #ffffff;
-  }
-
-  .dialog-btn {
-    position: absolute;
-    right: 0px;
-    top: 66px;
-  }
-
-  .position-tooltip {
-    margin-right: -10px;
-  }
-
-  .circle-rectangle {
-    width: 40px;
-    height: 30px;
-    border-radius: 50% 0% 0% 50%;
-    position: absolute;
-    right: 0px;
-    top: 66px;
-    background-color: white;
-    display: flex;
-  }
-
-  .shape-shadow {
-    filter: drop-shadow(5px 5px 10px rgb(102, 100, 100));
-  }
-  .left-icon {
-    margin-top: 7px;
-    margin-left: 8px;
-    margin-right: -7px;
-  }
-
-  .feedback-position {
-    position: absolute;
-    left: 460px;
-    top: 150px;
-    z-index: 9999;
-  }
-
-  :deep(.search-put .el-input__wrapper) {
-    background-color: #f0f2f5;
-  }
-  :deep(.el-popper__arrow) {
-    display: none;
-  }
-  :deep(.el-tree-node__content:hover) {
-    background: #e6f4ff;
-  }
-  :deep(.el-button--primary) {
-    --el-button-disabled-bg-color: #bfbfbf;
-  }
-  :deep(.el-popover) {
-    width: unset !important;
-    min-width: 110px;
-    text-align: center;
-    font-weight: 400;
-  }
-  ::v-deep.el-radio-button {
-    margin-right: 8px;
-    box-shadow: none;
-    border-radius: 4px !important;
-    border: 1px solid #d9d9d9 !important;
-  }
-</style>
-./MapBase/useCameraMap ./MapBase/CameraMapBak ./usePageConfig1

+ 59 - 0
src/views/page-config/component/mapContainer/LabelPoint.vue

@@ -0,0 +1,59 @@
+<template>
+  <div ref="mapRef" id="mapRef" class="test-icon" :style="{ display: 'flex', color: props.color }">
+    <svg width="26.762" fill="currentColor" height="32.282" xmlns="http://www.w3.org/2000/svg">
+      <defs>
+        <linearGradient x1="84.362%" y1="100%" x2="15.638%" y2="0%" id="prefix__a">
+          <stop stop-color="currentColor" offset="0%" />
+          <stop stop-color="currentColor" stop-opacity=".5" offset="100%" />
+        </linearGradient>
+        <!-- <linearGradient x1="50%" y1="0%" x2="50%" y2="98.062%" id="prefix__b">
+            <stop stop-color="#D3FFFA" offset="0%" />
+            <stop stop-color="#C1EDFF" offset="51.021%" />
+            <stop stop-color="#90E0FF" offset="100%" />
+        </linearGradient> -->
+      </defs>
+      <g fill="none" fill-rule="evenodd">
+        <path
+          d="M13.38.25c3.361 0 6.722 1.281 9.286 3.844a13.101 13.101 0 013.846 9.279c0 3.36-1.287 6.585-3.586 9.012l-.26.266-9.285 9.278-9.286-9.28A13.101 13.101 0 01.25 13.372c0-3.484 1.384-6.824 3.845-9.278A13.094 13.094 0 0113.381.25zm0 8.916a4.24 4.24 0 00-3.015 1.256 4.282 4.282 0 00-1.249 3.03 4.28 4.28 0 001.249 3.029 4.24 4.24 0 003.016 1.255 4.24 4.24 0 003.016-1.255 4.282 4.282 0 001.248-3.03 4.282 4.282 0 00-1.248-3.03 4.24 4.24 0 00-3.016-1.255z"
+          stroke-opacity=".498"
+          stroke="#B0E8FF"
+          stroke-width=".5"
+          fill-opacity=".9"
+          fill="currentColor"
+          xlink:href="url(#prefix__a)"
+        />
+        <ellipse fill="#FFFFFF" fill-rule="nonzero" cx="13.634" cy="13.619" rx="4.545" ry="4.54" />
+      </g>
+    </svg>
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { onMounted, ref } from 'vue';
+  import html2canvas from 'html2canvas';
+
+  const props = defineProps<{ color: string; getSvg: (url: string) => void }>();
+
+  const mapRef = ref<HTMLDivElement>();
+  const img = ref('');
+
+  onMounted(() => {
+    html2canvas(mapRef.value!, {
+      width: 27,
+      height: 32.5,
+      useCORS: true,
+      allowTaint: true,
+      backgroundColor: 'transparent',
+    }).then((canvas) => {
+      img.value = canvas.toDataURL('images/jpg');
+      props.getSvg(img.value);
+    });
+  });
+</script>
+
+<style scoped>
+  .test-icon {
+    /* width: 50px;
+    height: 50px; */
+  }
+</style>

+ 29 - 0
src/views/page-config/component/mapContainer/MapContainer copy.vue

@@ -0,0 +1,29 @@
+<template>
+  <div
+    :style="{
+      position: 'relative',
+      width: `${mapWidth}px`,
+      height: `${mapHeight}px`,
+      background: `url(${getRealImgUrl()})`,
+    }"
+  >
+    <Transformer v-for="item in showShops" :key="item.id" :shop="item" />
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { storeToRefs } from 'pinia';
+  import Transformer from './Transformer.vue';
+  import useMapEditor from '../../stores/useMapEditor';
+  import { useGlobSetting } from '@/hooks/setting';
+  import urlJoin from 'url-join';
+
+  const mapEditor = useMapEditor();
+  const { mapWidth, mapHeight, bgImg, showShops } = storeToRefs(mapEditor);
+
+  const globSetting = useGlobSetting();
+
+  const getRealImgUrl = () => urlJoin(globSetting.imgUrl!, bgImg.value);
+</script>
+
+<style scoped></style>

+ 39 - 11
src/views/page-config/component/mapContainer/MapContainer.vue

@@ -1,28 +1,56 @@
 <template>
 <template>
-  <div
-    :style="{
-      position: 'relative',
-      width: `${mapWidth}px`,
-      height: `${mapHeight}px`,
-      background: `url(${getRealImgUrl()})`,
-    }"
-  >
-    <Transformer v-for="item in showShops" :key="item.id" :shop="item" />
+  <div>
+    <v-stage :config="stageSize">
+      <v-layer ref="layerRef">
+        <v-image :config="bgConfig" />
+        <v-image :config="pointConfig" />
+      </v-layer>
+    </v-stage>
   </div>
   </div>
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
+  import { computed, ref } from 'vue';
   import { storeToRefs } from 'pinia';
   import { storeToRefs } from 'pinia';
-  import Transformer from './Transformer.vue';
   import useMapEditor from '../../stores/useMapEditor';
   import useMapEditor from '../../stores/useMapEditor';
   import { useGlobSetting } from '@/hooks/setting';
   import { useGlobSetting } from '@/hooks/setting';
   import urlJoin from 'url-join';
   import urlJoin from 'url-join';
+  import Konva from 'konva';
 
 
   const mapEditor = useMapEditor();
   const mapEditor = useMapEditor();
-  const { mapWidth, mapHeight, bgImg, showShops } = storeToRefs(mapEditor);
+  const { mapWidth, mapHeight, bgImg, showShops, bgImage, pointImage } = storeToRefs(mapEditor);
 
 
   const globSetting = useGlobSetting();
   const globSetting = useGlobSetting();
 
 
+  const layerRef = ref<Konva.Layer>();
+
+  const stageSize = computed(() => {
+    return {
+      width: mapWidth.value,
+      height: mapHeight.value,
+    };
+  });
+
+  const bgConfig = computed(() => {
+    return {
+      width: mapWidth.value,
+      height: mapHeight.value,
+      image: bgImage.value,
+      name: 'bg',
+    };
+  });
+
+  const pointConfig = computed(() => {
+    return {
+      x: 50,
+      y: 0,
+      width: 100,
+      height: 100,
+      image: pointImage.value,
+      name: 'pointSvg',
+    };
+  });
+
   const getRealImgUrl = () => urlJoin(globSetting.imgUrl!, bgImg.value);
   const getRealImgUrl = () => urlJoin(globSetting.imgUrl!, bgImg.value);
 </script>
 </script>
 
 

+ 111 - 0
src/views/page-config/stores/useMapEditor copy.ts

@@ -0,0 +1,111 @@
+import { ref } from 'vue';
+import { defineStore } from 'pinia';
+import { WorkShopInfoItem } from '@/api/scene/scene';
+import { cloneDeep } from 'lodash-es';
+import { useGlobSetting } from '@/hooks/setting';
+import urlJoin from 'url-join';
+
+export enum LabelPositionEnum {
+  LEFT = 'left',
+  RIGHT = 'right',
+  TOP = 'top',
+}
+
+export type EditStyle = {
+  x: number;
+  y: number;
+  scale: number;
+  thumbnail?: string;
+  bgColor: string;
+  fontSize: number;
+  fontColor: string;
+  posType: LabelPositionEnum;
+};
+
+export type MapWorkShopInfoItem = WorkShopInfoItem & EditStyle;
+
+export const useMapEditor = defineStore('home-map-ediotr', () => {
+  const globSetting = useGlobSetting();
+
+  const bgImg = ref('');
+  const mapWidth = ref(0);
+  const mapHeight = ref(0);
+  const addedShops = ref<MapWorkShopInfoItem[]>([]);
+  const showShops = ref<MapWorkShopInfoItem[]>([]);
+  const activeShop = ref<MapWorkShopInfoItem>({} as MapWorkShopInfoItem);
+
+  const addBg = () => {
+    const imgUrl = urlJoin(globSetting.imgUrl!, bgImg.value);
+    console.log(imgUrl);
+
+    const img = new Image();
+    img.onload = () => {
+      mapWidth.value = img.width;
+      mapHeight.value = img.height;
+    };
+    img.src = imgUrl;
+  };
+
+  const addShop = (shop: MapWorkShopInfoItem) => {
+    activeShop.value = shop;
+    addedShops.value.push(cloneDeep(shop));
+    showShops.value.push(cloneDeep(shop));
+  };
+
+  const toJson = () => {
+    const layout = {
+      bgInfo: {
+        width: mapWidth.value,
+        height: mapHeight.value,
+        img: bgImg.value,
+      },
+      shopList: addedShops.value.map((shop) => {
+        const temp = cloneDeep(shop) as any;
+        delete temp.children;
+        return temp;
+      }),
+    };
+
+    return JSON.stringify(layout);
+  };
+
+  const createMap = (layout) => {
+    bgImg.value = layout.bgInfo.img;
+    mapWidth.value = layout.bgInfo.width;
+    mapHeight.value = layout.bgInfo.height;
+    addedShops.value = cloneDeep(layout.shopList);
+    showShops.value = cloneDeep(layout.shopList);
+  };
+
+  const deleteShop = () => {
+    addedShops.value = addedShops.value.filter((item) => item.id !== activeShop.value.id);
+    showShops.value = showShops.value.filter((item) => item.id !== activeShop.value.id);
+    activeShop.value = {} as MapWorkShopInfoItem;
+  };
+
+  const resetMap = () => {
+    bgImg.value = '';
+    mapWidth.value = 0;
+    mapHeight.value = 0;
+    addedShops.value = [];
+    showShops.value = [];
+    activeShop.value = {} as MapWorkShopInfoItem;
+  };
+
+  return {
+    bgImg,
+    mapWidth,
+    mapHeight,
+    addedShops,
+    showShops,
+    activeShop,
+    addShop,
+    addBg,
+    toJson,
+    createMap,
+    deleteShop,
+    resetMap,
+  };
+});
+
+export default useMapEditor;

+ 33 - 6
src/views/page-config/stores/useMapEditor.ts

@@ -1,9 +1,11 @@
-import { ref } from 'vue';
+import { h, onMounted, ref, render } from 'vue';
 import { defineStore } from 'pinia';
 import { defineStore } from 'pinia';
 import { WorkShopInfoItem } from '@/api/scene/scene';
 import { WorkShopInfoItem } from '@/api/scene/scene';
 import { cloneDeep } from 'lodash-es';
 import { cloneDeep } from 'lodash-es';
 import { useGlobSetting } from '@/hooks/setting';
 import { useGlobSetting } from '@/hooks/setting';
 import urlJoin from 'url-join';
 import urlJoin from 'url-join';
+import LabelPoint from '../component/mapContainer/LabelPoint.vue';
+import emptyImg from '@/assets/images/table/table-empty.png';
 
 
 export enum LabelPositionEnum {
 export enum LabelPositionEnum {
   LEFT = 'left',
   LEFT = 'left',
@@ -34,16 +36,21 @@ export const useMapEditor = defineStore('home-map-ediotr', () => {
   const showShops = ref<MapWorkShopInfoItem[]>([]);
   const showShops = ref<MapWorkShopInfoItem[]>([]);
   const activeShop = ref<MapWorkShopInfoItem>({} as MapWorkShopInfoItem);
   const activeShop = ref<MapWorkShopInfoItem>({} as MapWorkShopInfoItem);
 
 
+  let tempDiv: HTMLDivElement;
+
+  /** konva elements refs */
+  const bgImage = ref<HTMLImageElement>(new Image());
+  const pointImage = ref<HTMLImageElement>(new Image());
+
   const addBg = () => {
   const addBg = () => {
     const imgUrl = urlJoin(globSetting.imgUrl!, bgImg.value);
     const imgUrl = urlJoin(globSetting.imgUrl!, bgImg.value);
     console.log(imgUrl);
     console.log(imgUrl);
 
 
-    const img = new Image();
-    img.onload = () => {
-      mapWidth.value = img.width;
-      mapHeight.value = img.height;
+    bgImage.value.onload = () => {
+      mapWidth.value = bgImage.value.width;
+      mapHeight.value = bgImage.value.height;
     };
     };
-    img.src = imgUrl;
+    bgImage.value.src = emptyImg;
   };
   };
 
 
   const addShop = (shop: MapWorkShopInfoItem) => {
   const addShop = (shop: MapWorkShopInfoItem) => {
@@ -83,6 +90,15 @@ export const useMapEditor = defineStore('home-map-ediotr', () => {
     activeShop.value = {} as MapWorkShopInfoItem;
     activeShop.value = {} as MapWorkShopInfoItem;
   };
   };
 
 
+  const getPointSvg = (url: string) => {
+    const svgImg = new Image();
+    svgImg.onload = () => {
+      pointImage.value = svgImg;
+      // tempDiv?.remove();
+    };
+    svgImg.src = url;
+  };
+
   const resetMap = () => {
   const resetMap = () => {
     bgImg.value = '';
     bgImg.value = '';
     mapWidth.value = 0;
     mapWidth.value = 0;
@@ -92,6 +108,15 @@ export const useMapEditor = defineStore('home-map-ediotr', () => {
     activeShop.value = {} as MapWorkShopInfoItem;
     activeShop.value = {} as MapWorkShopInfoItem;
   };
   };
 
 
+  onMounted(() => {
+    tempDiv = document.createElement('div') as HTMLDivElement;
+    tempDiv.setAttribute('style', `position: absolute; left: -3000px; top: 0px;`);
+    const pointSvg = h(LabelPoint, { color: 'red', getSvg: getPointSvg });
+    render(pointSvg, tempDiv);
+    const parentEl = document.getElementById('shopEditContainer') as HTMLDivElement;
+    parentEl.append(tempDiv);
+  });
+
   return {
   return {
     bgImg,
     bgImg,
     mapWidth,
     mapWidth,
@@ -99,6 +124,8 @@ export const useMapEditor = defineStore('home-map-ediotr', () => {
     addedShops,
     addedShops,
     showShops,
     showShops,
     activeShop,
     activeShop,
+    bgImage,
+    pointImage,
     addShop,
     addShop,
     addBg,
     addBg,
     toJson,
     toJson,