Jelajahi Sumber

Feat: 布局管理-场景布局 功能完善

“fujiacheng” 1 tahun lalu
induk
melakukan
24a3c2e9a9

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

@@ -2,6 +2,7 @@ export interface CompanyInfoList {
   id: number;
   name: string;
   layout: string;
+  imgUrl: string;
 }
 
 export interface CompanyLayoutInfoList {

+ 61 - 161
src/views/page-config/ConfigEdit.vue

@@ -4,49 +4,15 @@
       <div class="head-opt flex-1 flex justify-between items-center" style="margin-left: 0px">
         <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">
-          <div style="display: flex">
-            <div class="label-workshop" style="margin-left: 0px">选择标签:</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> -->
         <div class="top-bar">
-          <div class="back-btn" @click="router.back">
-            <img src="@/assets/rollback.png" />
-            <span>返回</span>
+          <div class="back-and-company">
+            <div class="back" @click="router.back">
+              <img src="@/assets/rollback.png" />
+              <span>返回</span>
+            </div>
             <div class="company-name">{{ companyName }}</div>
           </div>
-          <!-- <el-button @click="toJson">tojson</el-button> -->
           <div class="operation-btns">
             <el-upload
               class="avatar-uploader"
@@ -62,7 +28,7 @@
               <el-button :icon="Refresh"> 替换照片 </el-button>
             </el-upload>
 
-            <el-button :icon="Refresh" @click="clearLayout"> 重置 </el-button>
+            <el-button :icon="Refresh" @click="clearLayout"> 重置布局 </el-button>
 
             <el-button @click="handleSave" type="primary" :disabled="!companyId"> 保存 </el-button>
           </div>
@@ -81,9 +47,6 @@
             :suffix-icon="Search"
           />
         </div>
-        <!-- <span v-if="filterShopList.length == 0" class="ml-1" style="color: #3f3f3f">
-          提示:请先选择相应公司和图片
-        </span> -->
         <el-scrollbar style="position: relative; height: calc(100% - 90px)">
           <div
             v-for="item in filterShopList"
@@ -101,12 +64,7 @@
       </div>
       <div ref="drawContainer" id="drawContainer" class="draw-container">
         <div id="shopEditContainer" v-moveable:1 class="shop-edit-container">
-          <MapContainer
-            ref="mapContainerRef"
-            :is-mobile-view="isMobileView"
-            :module-code="labelMouduleCode"
-            @on-open="openConfig"
-          />
+          <MapContainer ref="mapContainerRef" :is-mobile-view="isMobileView" />
         </div>
         <el-upload
           v-if="!hasBg"
@@ -121,39 +79,15 @@
           :headers="getHeaders()"
         >
           <img src="~@/assets/images/img-upload.png" />
+          <div class="upload-tips-text"
+            >请上传1920*1080尺寸的布局背景图,其他尺寸会影响布局准确性!
+          </div>
         </el-upload>
       </div>
       <div class="shop-tag-edit-area" v-if="hasBg">
         <ShopTagEditArea @transformer-change="transformChange" />
       </div>
     </div>
-    <!-- <el-tooltip
-      class="box-item position-tooltip"
-      effect="dark"
-      content="显示侧边栏" 
-      :offset="12"
-      placement="left"
-    >
-      <div
-        v-if="leftShow && activeShopId && !isMobileView && activeShopId != -1"
-        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
-      v-if="!isMobileView"
-      ref="configDrawer"
-      @on-close="onClose"
-      @transformer-change="transformChange"
-      class="drawer-position"
-    /> -->
 
     <ConfigFinish
       :visible="visibleResult"
@@ -165,10 +99,9 @@
 </template>
 
 <script setup lang="ts">
-  import ConfigDialog from './component/ConfigDrawer.vue';
   import ConfigFinish from './component/ConfigFinish.vue';
   import { storeToRefs } from 'pinia';
-  import { ElMessage, ElInput, ElSwitch, ElMessageBox } from 'element-plus';
+  import { ElMessage, ElInput, ElMessageBox } from 'element-plus';
   import { onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue';
   import { getWorkshopListApi } from '@/api/scene/scene';
   import { computed } from 'vue';
@@ -182,17 +115,18 @@
   import urlJoin from 'url-join';
   import { useGlobSetting } from '@/hooks/setting';
   import { getHeaders } from '@/utils/http/axios';
-  import { CompanyLayoutType, LayoutType, ShopType } from '@/types/page-config/type';
+  import { ShopType } from '@/types/page-config/type';
   import ShopTagEditArea from './component/ShopTagEditArea.vue';
 
   const mapEditor = useMapEditor();
-  const { bgImg, addedShops, activeShopId, showShops } = storeToRefs(mapEditor);
+  const { bgImg, addedShops, activeShopId, showShops, mapHeight, mapWidth } =
+    storeToRefs(mapEditor);
   const { addShop, addBg, calcLayout, resetMap, createMap, deleteShop } = mapEditor;
 
   const router = useRouter();
 
   const pageConfig = usePageConfig();
-  const { scenesInfos, label, labelList, layoutId } = pageConfig;
+  const { label, layoutId } = pageConfig;
   const { urlPrefix } = useGlobSetting();
   const drawContainer = ref<HTMLDivElement>();
   const mapContainerRef = ref();
@@ -210,16 +144,17 @@
 
   const shopList = ref<ShopType[]>([]); // 车间列表
 
-  // // mapContainer宽高
-  // const mapContainerStageSize = reactive({ height: 0, width: 777 });
+  // mapContainer宽高
+  const mapContainerStageSize = reactive({ height: 0, width: 0 });
+  // // mapContainer缩放比例
+  const scale = computed(() =>
+    Math.min(
+      mapContainerStageSize.width / mapWidth.value,
+      mapContainerStageSize.height / mapHeight.value,
+    ),
+  );
 
   const handleBeforeUpload = (rawFile) => {
-    // if (!selectedCompany.value || !label.value) {
-    //   ElMessage.error({
-    //     message: '请先选择公司和标签',
-    //   });
-    //   return false;
-    // }
     if (
       rawFile.type !== 'image/jpg' &&
       rawFile.type !== 'image/jpeg' &&
@@ -236,18 +171,11 @@
     return index >= 0;
   };
 
-  //左边的浮动按钮
-  const leftShow = ref(true);
-  const onClose = (val) => {
-    leftShow.value = val;
-  };
-
   const transformChange = () => {
     mapContainerRef.value.updateLayoutTransformer();
   };
 
   const actionUrl = computed(() => {
-    // return urlJoin(urlPrefix, '/homepageConfig/updateCompanyPicture');
     return urlJoin(urlPrefix, '/admin/homepageConfig/uploadCompanyPicture');
   });
 
@@ -259,23 +187,6 @@
     { deep: true },
   );
 
-  const shadow = ref(false);
-  const shadowAdd = () => {
-    shadow.value = true;
-  };
-  const shadowRemove = () => {
-    shadow.value = false;
-  };
-
-  const configDrawer = ref();
-  const dialogReopen = () => {
-    configDrawer.value.openDialog();
-  };
-
-  const openConfig = () => {
-    configDrawer.value.openDialog();
-  };
-
   //总体的保存,将整个数据传过去
   const visibleResult = ref(false);
   const configStatus = ref(true);
@@ -290,23 +201,7 @@
     addBg();
   };
 
-  const labelMouduleCode = computed(() => {
-    return (
-      scenesInfos.value
-        .find((item) => item.id === companyId.value)
-        ?.labelModuleList.find((item) => item.sceneLabel.id === label.value)?.sceneModule.code ||
-      'default'
-    );
-  });
-
-  const changeShop = () => {
-    hasBg.value = false;
-    resetMap();
-    getShopContent();
-  };
-
   const getShopContent = () => {
-    // getCompanyLayoutApi({ companyId: selectedCompany.value || 2 }).then((res) => {
     getWorkshopListApi({ companyId: companyId.value! }).then((res) => {
       shopList.value = res;
     });
@@ -329,10 +224,7 @@
     });
   };
 
-  // const changeCompany = () => {
   const clearLayout = () => {
-    // label.value = undefined;
-
     ElMessageBox.confirm('是否重置当前设置?', '提示', {
       confirmButtonText: '确认',
       cancelButtonText: '取消',
@@ -347,7 +239,6 @@
     // 删除键
     if (e.keyCode === 46 || e.code === 'Delete') {
       deleteShop();
-      configDrawer.value.closeDialog();
     }
   };
 
@@ -367,7 +258,6 @@
     //如果已经存在车间,则禁止加入
     const existingCamera = addedShops.value.find((item) => item.id === shop.id);
     if (existingCamera) return;
-    // activeShopId.value = shop.id;
     if (!hasBg.value) {
       ElMessage.warning({
         message: '请先添加背景图片',
@@ -389,15 +279,11 @@
       posType: LabelPositionEnum.LEFT,
     };
     addShop(shopNode);
-    // dialogReopen();
   };
 
   const handleSave = () => {
-    const { json, scale } = mapContainerRef.value?.getLayout();
-    console.log('layout json', JSON.parse(json));
-    console.log('scale', scale);
-    const layout = hasBg.value === false ? '' : calcLayout(json, scale);
-    if (hasBg.value) console.log('Calc layout', JSON.parse(layout));
+    const { json } = mapContainerRef.value?.getLayout();
+    const layout = hasBg.value === false ? '' : calcLayout(json);
     const param = {
       layout,
       targetId: companyId.value || 2,
@@ -417,8 +303,17 @@
     }
   };
 
+  watch(
+    () => mapHeight.value,
+    () => {
+      mapContainerStageSize.height = document.getElementById('drawContainer')?.clientHeight!;
+      mapContainerStageSize.width = document.getElementById('drawContainer')?.clientWidth!;
+    },
+  );
+
   onBeforeUnmount(() => {
     window.removeEventListener('keydown', handleKeyDown);
+    window.onresize = null;
     resetMap();
   });
 
@@ -430,20 +325,16 @@
       companyName.value = String(routerParams.companyName);
       viewType.value = Number(routerParams.viewType);
     }
-    // if (routerParams.labelId) {
-    //   label.value = Number(routerParams.labelId);
-    // }
 
     window.addEventListener('keydown', handleKeyDown);
+    window.onresize = () => {
+      mapContainerStageSize.height = document.getElementById('drawContainer')?.clientHeight!;
+      mapContainerStageSize.width = document.getElementById('drawContainer')?.clientWidth!;
+    };
 
     if (companyId.value) {
       getShopContent();
     }
-
-    // console.log('clientHeight', document.getElementById('shopEditContainer')?.clientHeight);
-    // console.log('clientWidth', document.getElementById('shopEditContainer')?.clientWidth);
-    // mapContainerStageSize.height = document.getElementById('shopEditContainer')?.clientHeight!;
-    // mapContainerStageSize.width = document.getElementById('shopEditContainer')?.clientWidth!;
   });
 </script>
 
@@ -541,15 +432,15 @@
   .draw-container {
     height: calc(100vh - 160px);
     position: relative;
-    width: calc(100% - 600px);
     margin: 10px;
     overflow: hidden;
-    border: 1px solid #1890ff;
+    display: flex;
+    justify-content: center;
+    align-items: center;
   }
 
   .shop-edit-container {
-    height: 100%;
-    border: 1px solid #ff7118;
+    scale: v-bind(scale);
   }
 
   .drawer-position {
@@ -604,17 +495,22 @@
     display: flex;
     justify-content: space-between;
     align-items: center;
-    .back-btn {
+    .back-and-company {
       display: flex;
       align-items: center;
       gap: 10px;
-      cursor: pointer;
-      img {
-        width: 14px;
-        height: 14px;
-      }
-      span {
-        line-height: 14px;
+      .back {
+        display: flex;
+        align-items: center;
+        gap: 10px;
+        cursor: pointer;
+        img {
+          width: 14px;
+          height: 14px;
+        }
+        span {
+          line-height: 14px;
+        }
       }
       .company-name {
         font-weight: 600;
@@ -623,7 +519,7 @@
       }
     }
     .operation-btns {
-      width: 350px;
+      width: 380px;
       display: flex;
       align-items: center;
       .el-button {
@@ -637,6 +533,10 @@
     background-color: #ffffff;
   }
 
+  :deep(.el-upload) {
+    flex-direction: column;
+  }
+
   :deep(.search-put .el-input__wrapper) {
     background-color: #f0f2f5;
   }

+ 1 - 8
src/views/page-config/component/ShopTagEditArea.vue

@@ -1,7 +1,6 @@
 <template>
   <div class="shop-tag-edit-area">
     <div>
-      <!-- //这里要变选值 -->
       <div class="content-title"
         ><div class="uploader-title"> <div class="block"></div>标签设置</div>
       </div>
@@ -119,16 +118,14 @@
       <hr />
     </div>
     <div class="save-wrapper">
-      <!-- <el-button type="primary" @click="saveWorkshopConfig">保存</el-button> -->
       <el-button type="primary" @click="deleteShop">删除</el-button>
     </div>
   </div>
 </template>
 
 <script setup lang="ts">
-  import { computed, ref } from 'vue';
+  import { computed } from 'vue';
   import { storeToRefs } from 'pinia';
-  import { cloneDeep } from 'lodash-es';
   import { colorRGB2Hex } from '@/utils';
   import useMapEditor, { LabelPositionEnum, MapWorkShopInfoItem } from '../stores/useMapEditor';
 
@@ -152,10 +149,6 @@
     emit('transformerChange');
   };
 
-  const saveWorkshopConfig = () => {
-    addedShops.value = cloneDeep(showShops.value);
-  };
-
   const deleteShop = () => {
     const tempId = editShop.value.id;
     showShops.value = showShops.value.filter((x) => x.id != tempId);

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

@@ -12,17 +12,16 @@
           v-for="item in showShops"
           :key="item.id"
           :config="getGroupConfig(item)"
-          @dblclick="handleClick"
           @dragend="handleGroupDragend"
           @transform="handleTransform"
         >
-          <LabelItemMobile
+          <!-- <LabelItemMobile
             v-if="isMobileView"
             :shop="item"
             :is-mobile-view="isMobileView"
             :module-code="moduleCode"
-          />
-          <LabelItem v-else :shop="item" :is-mobile-view="isMobileView" />
+          /> -->
+          <LabelItem :shop="item" :is-mobile-view="isMobileView" />
         </v-group>
         <v-transformer ref="transformerRef" :config="transformerConfig" />
       </v-layer>
@@ -31,63 +30,36 @@
 </template>
 
 <script setup lang="ts">
-  import { computed, ref, reactive, watch, nextTick, onMounted } from 'vue';
+  import { computed, ref, watch, nextTick } from 'vue';
   import LabelItem from './LabelItem.vue';
   import LabelItemMobile from './LabelItemMobile.vue';
   import { storeToRefs } from 'pinia';
   import useMapEditor, { MapWorkShopInfoItem } from '../../stores/useMapEditor';
   import Konva from 'konva';
-  import { stages } from 'konva/lib/Stage';
 
   const mapEditor = useMapEditor();
-  const { showShops, addedShops, bgImage, activeShopId, mapHeight, mapWidth } =
-    storeToRefs(mapEditor);
+  const { showShops, addedShops, bgImage, activeShopId } = storeToRefs(mapEditor);
 
   const props = defineProps<{
     isMobileView: boolean;
-    moduleCode: string;
+    // moduleCode: string;
   }>();
 
-  const emit = defineEmits(['onOpen']);
-
   const stageRef = ref<Konva.Stage>();
   const layerRef = ref<Konva.Layer>();
   const transformerRef = ref<Konva.Transformer>();
 
-  // mapContainer宽高
-  const mapContainerStageSize = reactive({ height: 0, width: 0 });
-
   const stageSize = computed(() => {
     return {
-      height: mapContainerStageSize.height,
-      width: mapContainerStageSize.width,
-      // scale: scale,
-      // scaleY: scale,
-      // scaleX: scale,
-      // offsetX: mapContainerStageSize.width * 2,
-      // offsetY: mapContainerStageSize.height * 2,
-      // scaleY: 0.5,
-      // scaleX: 0.5,
-      // width: 1920,
-      // height: 1080,
+      height: 1080,
+      width: 1920,
     };
   });
 
-  const scale = computed(() =>
-    Math.min(
-      mapContainerStageSize.width / mapWidth.value,
-      mapContainerStageSize.height / mapHeight.value,
-    ),
-  );
-
   const bgConfig = computed(() => {
-    // const scale = Math.min(
-    //   mapContainerStageSize.width / mapWidth.value,
-    //   mapContainerStageSize.height / mapHeight.value,
-    // );
     return {
-      width: bgImage.value.width * scale.value,
-      height: bgImage.value.height * scale.value,
+      height: 1080,
+      width: 1920,
       image: bgImage.value,
       name: 'bg',
     };
@@ -186,10 +158,6 @@
     }
   };
 
-  const handleClick = () => {
-    emit('onOpen');
-  };
-
   watch(activeShopId, () => {
     nextTick(() => {
       updateTransformer();
@@ -198,48 +166,9 @@
 
   const getLayout = () => {
     const json = stageRef.value?.getStage().toJSON();
-    return { json, scale: scale.value };
+    return { json };
   };
 
-  onMounted(() => {
-    console.log('clientHeight', document.getElementById('shopEditContainer')?.clientHeight);
-    console.log('clientWidth', document.getElementById('shopEditContainer')?.clientWidth);
-    mapContainerStageSize.height = document.getElementById('shopEditContainer')?.clientHeight!;
-    mapContainerStageSize.width = document.getElementById('shopEditContainer')?.clientWidth!;
-  });
-
-  watch(
-    () => mapWidth.value,
-    (newValue) => {
-      console.log(
-        'mapWidth newValue',
-        newValue,
-        `mapContainerStageSize.width ${mapContainerStageSize.width}`,
-      );
-      console.log('mapWidth / mapContainerStageSize.width', mapContainerStageSize.width / newValue);
-    },
-  );
-  watch(
-    () => mapHeight.value,
-    (newValue) => {
-      console.log(
-        'mapHeight newValue',
-        newValue,
-        `mapContainerStageSize.height ${mapContainerStageSize.height}`,
-      );
-      console.log(
-        'mapHeight / mapContainerStageSize.height',
-        mapContainerStageSize.height / newValue,
-      );
-    },
-  );
-  watch(
-    () => stageSize.value,
-    (newValue) => {
-      console.log('stageSize.value newValue', newValue);
-    },
-  );
-
   defineExpose({ getLayout, updateLayoutTransformer });
 </script>
 

+ 3 - 24
src/views/page-config/stores/useMapEditor.ts

@@ -71,34 +71,13 @@ export const useMapEditor = defineStore('home-map-ediotr', () => {
     showShops.value.push(cloneDeep(shop));
   };
 
-  const calcLayout = (json: string, scale = 1) => {
+  const calcLayout = (json: string) => {
     const mapJson = safeParse(json);
     const mapData = mapJson.children[0].children.filter((item) => item.className === 'Group');
-    // const scale = Math.min(
-    //   document.getElementById('shopEditContainer')?.clientWidth! / mapWidth.value,
-    //   document.getElementById('shopEditContainer')?.clientHeight! / mapHeight.value,
-    // );
-
-    console.log('scale', scale);
 
     const shopListAdded = addedShops.value.map((item, index) => {
-      console.log('mapData[index].attrs.x', mapData[index].attrs.x);
-
-      console.log('mapHeight.value', mapHeight.value);
-      console.log('mapWidth.value', mapWidth.value);
-
-      console.log('(mapData[index].attrs.x / scale)', mapData[index].attrs.x / scale);
-
-      console.log('(1920 / mapWidth.value)', 1920 / mapWidth.value);
-
-      item.x = (mapData[index].attrs.x / scale) * (1920 / mapWidth.value) - 13;
-      item.y = (mapData[index].attrs.y / scale) * (1080 / mapHeight.value) + 3;
-      // item.x = mapData[index].attrs.x / scale;
-      // item.y = mapData[index].attrs.y / scale + 1
-      // 0;
-
-      // item.x = mapData[index].attrs.x;
-      // item.y = mapData[index].attrs.y;
+      item.x = mapData[index].attrs.x;
+      item.y = mapData[index].attrs.y;
       item.scaleX = mapData[index].attrs.scaleX || 1;
       item.scaleY = mapData[index].attrs.scaleY || 1;
       return item;