瀏覽代碼

修改本地版

tangning 5 月之前
父節點
當前提交
e51a22e771
共有 48 個文件被更改,包括 1225 次插入475 次删除
  1. 1 0
      package.json
  2. 41 4
      src/assets/style/custom-element.scss
  3. 36 8
      src/components/viewImg/index.vue
  4. 1 1
      src/config/locale.vue
  5. 4 2
      src/constant/scene.ts
  6. 1 0
      src/core/Scene.js
  7. 1 1
      src/hook/upload.ts
  8. 1 1
      src/i18n/index.ts
  9. 11 2
      src/i18n/weblate/en.json
  10. 11 2
      src/i18n/weblate/ja.json
  11. 300 65
      src/i18n/weblate/kr.json
  12. 16 5
      src/i18n/weblate/zh.json
  13. 4 1
      src/request/config.ts
  14. 15 4
      src/request/index.ts
  15. 9 9
      src/request/urls.ts
  16. 6 6
      src/router/config.ts
  17. 49 37
      src/store/case.ts
  18. 2 0
      src/store/scene.ts
  19. 20 8
      src/store/user.ts
  20. 2 0
      src/view/abstract/index.vue
  21. 9 3
      src/view/aiList/index.vue
  22. 11 3
      src/view/case/caseFile.vue
  23. 10 2
      src/view/case/draw/board/editCAD/Service/FloorplanService.js
  24. 2 1
      src/view/case/draw/board/useBoard.ts
  25. 6 4
      src/view/case/draw/index.vue
  26. 2 3
      src/view/case/draw/selectFuseImage.vue
  27. 1 1
      src/view/case/draw/selectMapImage.vue
  28. 129 48
      src/view/case/draw/selectMapImage copy.vue
  29. 2 0
      src/view/case/draw/slider.vue
  30. 31 10
      src/view/case/help.ts
  31. 3 3
      src/view/case/photos/draggable.vue
  32. 18 12
      src/view/case/photos/index.vue
  33. 1 1
      src/view/case/quisk.ts
  34. 5 2
      src/view/case/records/index.vue
  35. 2 1
      src/view/case/records/manifest.vue
  36. 2 2
      src/view/dossier/index.vue
  37. 1 1
      src/view/layout/index.vue
  38. 73 30
      src/view/layout/top/index.vue
  39. 2 0
      src/view/layout/top/style.scss
  40. 21 12
      src/view/material/index.vue
  41. 307 169
      src/view/mediaLibrary/TableComponent.vue
  42. 1 1
      src/view/originalPhoto/addCaseFile.vue
  43. 1 1
      src/view/originalPhoto/addLibrary.vue
  44. 2 2
      src/view/other/index.vue
  45. 11 5
      src/view/vrmodel/index.vue
  46. 1 1
      src/view/vrmodel/sceneContent.vue
  47. 1 1
      vite.config.ts
  48. 39 0
      yarn.lock

+ 1 - 0
package.json

@@ -37,6 +37,7 @@
     "vue-draggable-plus": "^0.6.0",
     "vue-i18n": "^11.1.1",
     "vue-router": "^4.2.4",
+    "vue3-google-map": "^0.21.1",
     "windicss": "^3.5.6"
   },
   "devDependencies": {

+ 41 - 4
src/assets/style/custom-element.scss

@@ -15,13 +15,13 @@ html.dark {
   --el-color-primary-light-9: rgb(22 36 34);
   --el-color-primary-light-7: rgb(0 106 93);
   --topbgColor: #131313;
-  .title1, .title0{
+  .title1, .title0, .def-map-info p, .content-layout h4{
     color: #fff !important;
   }
   .layer .content .view .main{
     background-color: var(--bgColor);
   }
-  .slide, .el-menu-item{
+  .slide, .el-menu-item, .records, .records .header{
     background-color: var(--bgColor);
   }
   .photo .left {
@@ -30,11 +30,48 @@ html.dark {
   .header-top{
     background-color: var(--topbgColor);
   }
-  .el-button{
+  .content-table th {
+    background: none;
+  }
+  .el-button--primary{
     &:hover{
-      background: none;
+      background-color: #009e8c;
+      border-color: #009e8c;
+      color: #009e8c
+    }
+  }
+  .mandatory .el-form-item__label::before
+  {
+    color: red
+  }
+  .el-checkbox__input.is-disabled .el-checkbox__inner{
+    background: #fff;
+  }
+  .df-layout-child {
+    .el-form-item__label {
+      color: var(--el-fill-color-blank);
     }
+    .el-textarea__inner, .el-color-picker__color-inner, .el-color-picker__icon {
+      background: #fff;
+      color: #000;
+    }
+    .el-slider__runway{
+      background-color: #e5e4e4;
+    }
+  }
+  .amap-info {
+    color: #000;
+  }
+  .tools{
+    .el-button{
+      background: #e7fffc;
+      &:hover{
+        border-color: #009e8c;
+        color: #009e8c
+      }
 
+    }
+    // --el-fill-color-blank: #e7fffc;
   }
 }
 

+ 36 - 8
src/components/viewImg/index.vue

@@ -30,10 +30,10 @@
           </el-image>
         </div>
         <div class="mask">
-          <div  class="zoomInImg" v-if="TypeFilter(item.filesUrl) == '.mp4'" @click="showVideoView(item.filesUrl)">
+          <div  class="zoomInImg" v-if="TypeFilter(item.filesUrl) == '.mp4'" @click="showVideoView(urlFilter(item.filesUrl))">
             <el-icon color="#fff"><VideoPlay /></el-icon>
           </div>
-          <div v-else class="zoomInImg" @click="showImgView(item.filesUrl)">
+          <div v-else class="zoomInImg" @click="showImgView(getUrlSrc({type: 102}) +'/' + item.filesUrl, item.showImgView)">
             <el-icon color="#fff"><zoom-in /></el-icon>
           </div>
           <div
@@ -70,6 +70,8 @@ import { router } from "@/router";
 import { ElMessage, ElMessageBox } from "element-plus";
 import fileImg from "@/assets/svg/file.svg";
 import { delCaseFile, } from "@/store/caseFile";
+import { getUrlSrc } from "@/store/case";
+import { windowOpen } from "@/util";
 const caseId = computed(() => {
   const caseId = router.currentRoute.value.params.caseId;
   if (caseId) {
@@ -135,7 +137,7 @@ const initFileList = ref([
 const urlFilter = (url) => {
   if (!url) return "";
   const fileType = url.substring(url.lastIndexOf(".")).toLowerCase();
-  return fileList.value.includes(fileType) ? fileImg : url ;
+  return fileList.value.includes(fileType) ? fileImg : (getUrlSrc({type: 102}) +'/' + url) ;
  }
  
 const TypeFilter = (url) => {
@@ -150,10 +152,14 @@ const TypeFilter = (url) => {
 }
 watchEffect(() => {
   if (props.list.length) {
-    let newlist = props.list.map(item => item.filesUrl || item.fileUrl);
-    initFileList.value = newlist.filter(item => {
-      return verifySuffix(item)
+    let newlist = props.list.map(item => {
+      return getUrlSrc({type: 102}) +'/' + item.filesUrl || item.fileUrl
     });
+    initFileList.value = newlist
+    // newlist.filter(item => {
+    //   return verifySuffix(item)
+    // });
+    console.log(newlist, "newlist", initFileList.value);
   }
 });
 
@@ -162,22 +168,44 @@ const showViewer = ref(false);
 const previewList = ref([]);
 
 const showVideoView = (src) => {
+  console.log(src, "showVideoView");
   visible.value.show = true;
   visible.value.src = src;
 }
-  const showImgView = (src) => {
+  const showImgView = (src, filesTitle) => {
   console.log(src, "showImgView", initFileList.value);
   const fileType = src.substring(src.lastIndexOf(".")).toLowerCase();
   urlindex.value = initFileList.value.findIndex((item) => item === src);
   console.log(fileType, fileList.value.includes(fileType), "urlFilter");
   if(fileList.value.includes(fileType)){
-    window.open(window.location.origin + src);
+    let fileName = filesTitle + fileType
+    downloadFile(src, fileName)
+    // windowOpen();
     return;
   }
   // url.value = src;
   showViewer.value = true;
   // previewList.value = [src];
 };
+function getExtension (name) {
+      return name.substring(name.lastIndexOf("."))
+}
+function downloadFile(sourceUrl, fileName,) {
+  console.log("downloadFile", sourceUrl, fileName + getExtension(sourceUrl));
+  const link = document.createElement('a');
+  // link.style.display = 'none';
+  // 设置下载地址
+  link.href = getUrlSrc({type: 102}) + '/' + sourceUrl;
+  link.download = fileName + getExtension(sourceUrl);
+  link.click();
+  link.remove();
+  // link.setAttribute('href', sourceUrl);
+  // 设置文件名
+  // link.setAttribute('download', fileName + getExtension(sourceUrl));
+  // document.body.appendChild(link);
+  // link.click();
+  // document.body.removeChild(link);
+}
 const handleItem = (type, item) => {
   if(type == 'delete'){
     del(item)

+ 1 - 1
src/config/locale.vue

@@ -12,6 +12,6 @@ import en from 'element-plus/dist/locale/en.mjs';
 const lang = ui18n.locale
 const locale = computed(() => {
   console.log('computedlang', lang);
-  return lang === "zh" ? zh : en;
+  return lang == "en" ? en : zh;
 });
 </script>

+ 4 - 2
src/constant/scene.ts

@@ -44,8 +44,10 @@ export const QuoteSceneStatusDesc: { [key in QuoteSceneStatus]: string } = {
   [QuoteSceneStatus.RUN]: ui18n.t('program.case.status_0') || "计算中",
   [QuoteSceneStatus.ERR]: ui18n.t('program.case.status_-1') ||"计算失败",
   [QuoteSceneStatus.SUCCESS]: ui18n.t('program.case.status_2') ||"计算成功",
-  [QuoteSceneStatus.ARCHIVE]: ui18n.t('program.case.status_3') || "封存",
-  [QuoteSceneStatus.RERUN]: ui18n.t('program.case.status_4') || "重新计算中",
+  [QuoteSceneStatus.ARCHIVE]: ui18n.t('program.sceneDown.coverStatus.un') || "封存",
+  [QuoteSceneStatus.ING]: ui18n.t('program.sceneDown.coverStatus.pause') || "封存",
+  [QuoteSceneStatus.RERUN]: ui18n.t('program.case.status_0') || "重新计算中",
+  [QuoteSceneStatus.LINEUP]: ui18n.t('program.sceneDown.coverStatus.lineup') || "重新计算中",
 };
 
 export const ModelSceneStatusDesc: { [key in ModelSceneStatus]: string } = {

+ 1 - 0
src/core/Scene.js

@@ -250,6 +250,7 @@ export default class Scene extends Mitt {
     this.renderer.render(this.scene, this.orthCamera, null, false);
 
     const dataURL = this.renderer.domElement.toDataURL("image/jpeg");
+    console.log("dataURL", dataURL);
     this.blobScreens.push(dataURItoBlob(dataURL));
     console.log(this.width, this.height);
     if (typeof index === "number") {

+ 1 - 1
src/hook/upload.ts

@@ -56,7 +56,7 @@ export const useUpload = <T>(props: UploadProps<T>) => {
     const fileType = file.name
       .substring(file.name.lastIndexOf("."))
       .toUpperCase();
-    console.log('props.formats', props.formats, format.value);
+    console.log('props.formats', props, format.value);
     if (!props.formats.some((type) => type.toUpperCase() === fileType)) {
       let newformat = format.value.replaceAll('.', "") as string;
       ElMessage.error(`请上传${newformat}`);

+ 1 - 1
src/i18n/index.ts

@@ -51,7 +51,7 @@ const getAllParams = () => {
   return obj;
 };
 const params = getAllParams()
-export const lang = (params.lang || window.lang || local.get(localKey))
+export const lang = (params.ga == 'true'?'zh':params.lang || window.lang || local.get(localKey))
 console.log('strToParams',params, lang, local.get(localKey))
 
 if (lang !== local.get(localKey)) {

+ 11 - 2
src/i18n/weblate/en.json

@@ -310,7 +310,15 @@
             "num": "Scene code",
             "sceneType": "Scene type",
             "query": "Query",
-            "caseTitle": "Case name"
+            "caseTitle": "Case name",
+            "updateTime": "Update time",
+            "status_3": "Archive",
+            "status_0": "Calculating",
+            "miss_case_title": "Please enter the case name!",
+            "miss_case": "Please select scene!",
+            "status_4": "Recalculating",
+            "status_-1": "Calculation Failed",
+            "status_2": "Calculation successful"
         },
         "calcFailureTip": "Are you sure you want to recalculate? Some actions cannot be undone. Note: Recalculating the point cloud scene will clear some data, such as merged datasets, hotspots, etc. Please proceed with caution."
     },
@@ -1099,7 +1107,8 @@
         "yc": "Remove",
         "addScene": "Add Scene",
         "delTops": "Are you sure you want to delete?",
-        "ycTips": "Are you sure you want to remove the current scene?"
+        "ycTips": "Are you sure you want to remove the current scene?",
+        "sceneName": "Scene name"
     },
     "mediaLibrary": {
         "title": "Media Library",

+ 11 - 2
src/i18n/weblate/ja.json

@@ -358,7 +358,15 @@
             "num": "コード",
             "sceneType": "シーンタイプ",
             "query": "検索",
-            "caseTitle": "事件名"
+            "caseTitle": "事件名",
+            "updateTime": "アップデート時間",
+            "status_3": "アーカイブ",
+            "status_0": "計算中",
+            "miss_case_title": "事件名を入力してください",
+            "miss_case": "シーンを選択してください",
+            "status_4": "再計算中",
+            "status_-1": "計算失敗",
+            "status_2": "計算成功"
         },
         "multiLang": "多言語",
         "migrateScene": {
@@ -1099,7 +1107,8 @@
         "yc": "削除",
         "caseView": "案件をプレビュー",
         "delTops": "キャンセルを確定しますか",
-        "ycTips": "現在のシーンを本当に削除しますか?"
+        "ycTips": "現在のシーンを本当に削除しますか?",
+        "sceneName": "物件名"
     },
     "mediaLibrary": {
         "Modeling": "傾斜撮影",

+ 300 - 65
src/i18n/weblate/kr.json

@@ -24,17 +24,25 @@
             "toUsbIng": "U드라이브로 동기화 중입니다.",
             "win": "컴퓨터",
             "winScene": "컴퓨터 로컬 씬",
-            "delTipPrev": "이 씬을 다음에서 삭제하시겠습니까?"
+            "delTipPrev": "이 씬을 다음에서 삭제하시겠습니까?",
+            "linkTip": "USB와 컴퓨터를 연결 상태로 유지해 주세요!",
+            "sy": "예상 남은 시간/수량",
+            "repeatTipPrev": "다시 동기화하면 현재 내용이 덮어씌워집니다.",
+            "reSync": "다시 동기화",
+            "exit": "프로그램을 종료하다"
         },
         "syncHelp": {
             "step1": {
                 "desc1": "전용 USB 드라이브를 컴퓨터 USB 포트에 꽂아요.",
-                "desc2": "식별을 기다려요"
+                "desc2": "식별을 기다려요",
+                "title": "연결 단계"
             },
             "step2": {
                 "desc1": "USB 포트를 교체하고 다시 시도해요.",
-                "desc2": "USB 디스크 이름이 수정되었는지 확인해 주세요."
-            }
+                "desc2": "USB 디스크 이름이 수정되었는지 확인해 주세요.",
+                "title": "USB 메모리를 연결할 수 없어요?"
+            },
+            "title": "그림과 단계를 참고하여 USB를 연결해 주세요"
         },
         "camera": {
             "sn": "SN 코드",
@@ -46,7 +54,9 @@
             "changeAuth": "변경이 성공적으로 완료되었습니다. 카메라에서 최신 결과를 확인해 주세요.",
             "lastTime": "마지막 동기화 시간",
             "sync": "카메라 인증",
-            "cameraType": "카메라 유형"
+            "cameraType": "카메라 유형",
+            "authErr": "등록 코드 가져오기에 실패했습니다. 관리자 권한으로 프로그램을 실행해 주세요!",
+            "auth": "인증에 성공했습니다. 최신 인증 결과를 확인해 주세요."
         },
         "delete": "삭제",
         "auth": {
@@ -67,7 +77,8 @@
             "uAuthTipOut": "귀하의 소프트웨어 인증이 만료되었습니다.",
             "success": "인증 성공!",
             "sn": "인증 코드",
-            "status": "상태"
+            "status": "상태",
+            "timeoutStatus": "만료됨"
         },
         "errMsg": {
             "genObjTip": "망사 프로젝트는 계산중이니 기다려 주세요.",
@@ -94,7 +105,10 @@
             "coverStatus": {
                 "copy": "복사 중입니다.",
                 "un": "계산 대기 중",
-                "lineup": "대기 중입니다."
+                "lineup": "대기 중입니다.",
+                "ing": "계산 중입니다.",
+                "err": "계산에 실패했습니다.",
+                "pause": "계산 일시 정지"
             },
             "e57Gen": "e57 생성 중입니다.",
             "reset": "재생성 중입니다.",
@@ -103,11 +117,18 @@
             "msgStatus": {
                 "ing": "일시 정지",
                 "lineup": "일시 정지",
-                "copy": "복사 중입니다."
+                "copy": "복사 중입니다.",
+                "un": "계산",
+                "com": "편집",
+                "err": "재계산",
+                "pause": "재계산"
             },
             "e57GenIng": "e57 생성 중입니다.",
             "obgGen": "Obj 생성 중입니다.",
-            "recalcMsg": "재계산을 진행하시겠습니까?"
+            "recalcMsg": "재계산을 진행하시겠습니까?",
+            "edit": "편집",
+            "fush": "합성",
+            "recalc": "재계산"
         },
         "scene": {
             "laserObj": "망사 프로젝트",
@@ -118,7 +139,9 @@
             "unSearch": "현재 씬이 없습니다. 먼저 씬 데이터를 동기화해 주세요~~",
             "unKeySearch": "씬을 찾을 수 없습니다.",
             "laserClo": "점 클라우드 씬",
-            "calcTip": "Obj 씬 생성에는 시간이 걸릴 수 있습니다. 잠시만 기다려 주세요."
+            "calcTip": "Obj 씬 생성에는 시간이 걸릴 수 있습니다. 잠시만 기다려 주세요.",
+            "calc": "씬 계산",
+            "editReCalc": "편집 중인 씬이 재계산되었습니다."
         },
         "errCode": {
             "204": "중복 전송하지 마십시오. 나중에 다시 시도하십시오",
@@ -132,7 +155,8 @@
             "3106": "카메라 인증 키가 현재 장치와 일치하지 않습니다.",
             "3113": "카메라 인증 키가 유효하지 않습니다.",
             "3110": "카메라 키 교체는 재사용할 수 없습니다.",
-            "3111": "카메라 키 교체가 유효하지 않습니다."
+            "3111": "카메라 키 교체가 유효하지 않습니다.",
+            "3107": "인증에 성공했습니다. 최신 인증 결과를 확인해 주세요."
         },
         "exit-msg": "페이지를 닫아도 프로그램은 백그라운드에서 계속 실행됩니다. 종료하려면 우측 하단 트레이에서 프로그램 종료를 선택해 주세요.",
         "OpenFile": {
@@ -141,7 +165,8 @@
         "rocre": {
             "cancel": "취소",
             "screen": "스크린",
-            "ok": "확인"
+            "ok": "확인",
+            "title": "녹화할 스크린을 선택해 주세요"
         },
         "sceneDetail": {
             "e57_title": "E57",
@@ -164,7 +189,15 @@
             "export": "생성",
             "unPacking": "패킹되지 않음",
             "unGenerate": "생성되지 않았습니다.",
-            "offline_title": "오프라인 패키지"
+            "offline_title": "오프라인 패키지",
+            "calc_done_time": "계산 완료 시간",
+            "upload_time": "계산 시간",
+            "buildTime": "계산 시간",
+            "calcing": "계산 중입니다.",
+            "migrageS_title": "장면 이전",
+            "re_generate": "다시 생성",
+            "re_calc_done_time": "재계산 완료 시간",
+            "buildEndTime": "재계산 완료 시간"
         },
         "multiLang": "다국어",
         "menu": {
@@ -182,7 +215,13 @@
             "ingSelectTip": "진행 중인 작업이 있어 현재 경로 추가를 지원하지 않습니다.",
             "about": "버전 정보",
             "install": "상태 표시",
-            "camera": "카메라 관리"
+            "camera": "카메라 관리",
+            "layout": "시스템",
+            "mainLayout": "시스템",
+            "secoundLayout": "시스템",
+            "settingLayout": "시스템 설정",
+            "langTip": "선택한 경로에 중국어를 포함해서는 안 된다",
+            "selectTitle": "장면 저장 경로를 선택하세요"
         },
         "migrateScene": {
             "fail_import": "가져오기 실패, 올바른 씬 패키지 데이터를 업로드해 주세요."
@@ -194,7 +233,15 @@
             "num": "씬 코드",
             "sceneType": "씬 유형",
             "query": "조회",
-            "caseTitle": "케이스 이름"
+            "caseTitle": "케이스 이름",
+            "updateTime": "업데이트 시간",
+            "status_3": "봉인",
+            "status_0": "계산 중입니다.",
+            "status_-1": "계산에 실패했습니다.",
+            "status_2": "계산 성공",
+            "miss_case_title": "케이스 이름을 입력해 주세요!",
+            "miss_case": "씬을 선택해 주세요!",
+            "status_4": "재계산 중입니다."
         },
         "cantcal": {
             "content": {
@@ -207,13 +254,17 @@
         },
         "syncStatus": {
             "zip": "압축 중입니다.",
-            "copy": "복사 중입니다."
+            "copy": "복사 중입니다.",
+            "unzip": "압축 해제 중입니다."
         },
         "no": "아니오",
         "fileManage": {
             "originTitle": "원본 자료",
             "openDir": "파일 탐색기 열기",
-            "addStorage": "새 경로 추가"
+            "addStorage": "새 경로 추가",
+            "calcTitle": "계산 결과",
+            "storage": "기본 저장 경로",
+            "defaultStorage": "경로"
         },
         "shenguang": "4DKanKan Meta",
         "cameraAuth": {
@@ -225,7 +276,14 @@
             "changeKey": "카메라 키 변경",
             "code": "머신 코드",
             "title": "카메라 인증",
-            "cameraKey": "카메라 인증 키"
+            "cameraKey": "카메라 인증 키",
+            "systemTitle": "먼저 설치 인증을 진행해 주세요",
+            "keyTip": "포인트 클라우드 영역으로 이동한 후 다시 시도해 주세요",
+            "systemKeyTip": "4DAGE 공식 판매 채널에 연락하여 시스템 권한 Key를 획득하시기 바랍니다.",
+            "placeholder": "입력해 주세요",
+            "nullTip": "카메라 권한 Key를 입력해 주세요",
+            "systemNullTip": "시스템 권한 Key를 입력해 주세요",
+            "submit": "검증"
         },
         "deskErr": "현재 자원이 있는 하드 드라이브의 공간이 부족합니다. 새로운 저장 경로를 추가하여 사용에 지장이 없도록 해 주세요.",
         "rejectSceneSync": "현재 씬은 카메라 인증 목록에 없습니다!",
@@ -241,7 +299,8 @@
                 "err": "압축 파일이 올바르지 않습니다.",
                 "err2": "치명적인 오류가 발생했습니다. 파일이 보안 소프트웨어에 의해 점유되었을 가능성이 있습니다.",
                 "err1": "일부 파일이 사용 중일 수 있습니다.",
-                "err7": "클라이언트에 기능이 누락되었습니다."
+                "err7": "클라이언트에 기능이 누락되었습니다.",
+                "err8": "충분한 저장 공간이 있는지 확인해 주세요."
             }
         },
         "the3PartyOpenAlready": "현재 프로그램이 열려 있습니다!",
@@ -252,7 +311,16 @@
         "deskRmTip": "이 작업은 경로만 삭제되며, 로컬 폴더는 삭제되지 않습니다. 그러나 폴더 내 리소스는 더 이상 접근할 수 없게 됩니다.",
         "lackSuperPower": "권한이 부족합니다. 관리자 권한으로 실행해 주세요.",
         "calcFailureTip": "재계산을 진행하시겠습니까? 일부 작업은 취소할 수 없습니다.  \n주의: 점 클라우드 씬의 재계산은 병합된 데이터셋, 핫스팟 등의 일부 데이터를 삭제하므로 신중하게 진행해 주세요.",
-        "the3PartyWarmTip": "제3자 애플리케이션이 열리고 있습니다. 잠시 후 제3자 애플리케이션으로 이동해 주세요!"
+        "the3PartyWarmTip": "제3자 애플리케이션이 열리고 있습니다. 잠시 후 제3자 애플리케이션으로 이동해 주세요!",
+        "continueDown": "다운로드 계속",
+        "iframe": {
+            "un": "장면 관리에서 열고 싶은 장면을 선택해 주세요",
+            "sync": "편집 중인 씬이 재계산되었습니다."
+        },
+        "lang": "언어",
+        "jxcalc": "계산을 계속 진행하시겠습니까?",
+        "back": "돌아가다",
+        "linkUP": "USB 메모리를 연결하다"
     },
     "common": {
         "sure": "확정",
@@ -284,7 +352,16 @@
         "Noavailablemodels": "사용 가능한 모델을 감지할 수 없습니다.",
         "AddPerspectives": "시점 추가",
         "moveFail": "파일 이동에 실패했습니다. 충분한 저장 공간이 있는지 확인해 주세요.",
-        "Delete": "이 화면을 삭제하시겠습니까?"
+        "Delete": "이 화면을 삭제하시겠습니까?",
+        "Continuerecording": "녹화 계속",
+        "Keepadding": "추가 계속",
+        "time": "비디오 길이",
+        "edit": "편집",
+        "back": "나가다",
+        "entertitle": "인기 있는 이슈 제목을 입력해 주십시오",
+        "route": "경로",
+        "Leftoversites": "유류 부위",
+        "rename": "이름 변경"
     },
     "coord": {
         "edit": {
@@ -303,7 +380,19 @@
             "noRepeatUpdate": "조정 방법 보기",
             "gisDataErr": "데이터가 올바르지 않습니다. 지리 좌표 p1, p2 형식이 정확한지, 값이 동일한지 확인해 주세요!",
             "localDataErr": "데이터가 올바르지 않습니다. 로컬 좌표 p1, p2 형식이 정확한지, 값이 동일한지 확인해 주세요!",
-            "diff": "차이"
+            "diff": "차이",
+            "unsetCtrls": "먼저 제어점을 설정해 주세요",
+            "setPoint": "P {index}로 설정",
+            "pointEqual": "P1과 P2에 동일한 수치를 입력하지 마세요",
+            "setCtrls": "제어점 설정",
+            "trapLocalPoint": "장면에서 마우스 오른쪽 버튼을 클릭하여 제어점의 로컬 좌표를 설정해 주세요",
+            "userUseMouse": "사용자 정의 (씬에서 오른쪽 클릭 선택)",
+            "placeholder": "입력해 주세요",
+            "placeholderDMS": "도(°)를 입력해 주세요.",
+            "placeholderD": "도(°)를 입력해 주세요",
+            "inputGis": "이 좌표계에서 제어점의 지리 좌표를 입력해 주세요",
+            "ggmap": "구글 지도",
+            "gmap": "아모이 지도"
         },
         "manageTitle": "지리 등록",
         "name": "좌표",
@@ -315,10 +404,16 @@
             "pro": "투영 좌표",
             "webMercator": "지구 좌표",
             "screen": "스크린 좌표",
-            "local": "로컬 좌표"
+            "local": "로컬 좌표",
+            "amap": "아모이 좌표",
+            "gmap": "구글 좌표"
         },
         "copy": "좌표 복사",
-        "ctrls": "제어점"
+        "ctrls": "제어점",
+        "lat": "위도",
+        "lng": "경도",
+        "height": "고도",
+        "selectType": "좌표 유형을 선택하세요"
     },
     "view": {
         "scene": "3D",
@@ -327,7 +422,8 @@
         "density": {
             "low": "낮",
             "middle": "중",
-            "name": "점 클라우드 품질"
+            "name": "점 클라우드 품질",
+            "high": "높이"
         },
         "sideLeft": "측면 보기( N-S)",
         "switchView": "보기 전환",
@@ -336,7 +432,8 @@
         "colorMode": {
             "translucent": "반투명",
             "full": "컬러",
-            "altitude": "해발"
+            "altitude": "해발",
+            "name": "색상 모드"
         },
         "strong": "엣지 강화",
         "reset": "기본값으로 복원",
@@ -352,7 +449,12 @@
         "showMini": "미니 뷰 표시",
         "showGaodeMap": "아모이 지도 표시",
         "clound": "탐색 뷰",
-        "cloudSeting": "점 클라우드 설정"
+        "cloudSeting": "점 클라우드 설정",
+        "detail": "세부 사항",
+        "range": "범위",
+        "moreSetting": "고급 설정",
+        "seting": "뷰 설정",
+        "top": "상단 뷰"
     },
     "dataset": {
         "recalcJoinDeleteTip": "【{sceneName}】 가 다시 계산되었고 당신이 추가한 【{title}】 데이터셋은 삭제되었습니다",
@@ -375,7 +477,9 @@
             "name": "공간 이름",
             "titleConfirm": "공간 이름이 비어 있어 저장할 수 없습니다.",
             "showTitle": "공간 데이터",
-            "title": "공간 모델"
+            "title": "공간 모델",
+            "set": "공간 모델 편집",
+            "area": "면적"
         },
         "refer": "참조 데이터셋",
         "deleteJoinDeleteTip": "【{sceneName}】이(가) 삭제되었습니다. 추가하신 데이터셋 【{title}】도 함께 삭제되었습니다.",
@@ -391,7 +495,11 @@
         "unJoinDatasets": "귀하의 계정에는 추가할 수 있는 데이터셋이 없습니다.",
         "backCalc": "백그라운드에서 계산 중입니다…",
         "setting": {
-            "subtle": "미세 조정"
+            "subtle": "미세 조정",
+            "setName": "데이터셋 편집",
+            "viewCenter": "뷰 중앙 맞춤",
+            "lockTip": "해당 데이터 세트가 제어점에 의해 잠겼습니다",
+            "subtleTip": "오른쪽 패널에서 미세 조정이 필요한 데이터 세트를 선택해 주세요"
         },
         "manageTitle": "데이터셋 관리",
         "title": "데이터셋",
@@ -403,7 +511,13 @@
         "uploadSBtn": "로컬 업로드",
         "format": "형식",
         "pointNum": "점 개수",
-        "joinBtn": "지금 병합"
+        "joinBtn": "지금 병합",
+        "calibration": {
+            "gotoTip": "데이터 세트를 보정하여 장면에서 올바르게拼接되도록 해주세요."
+        },
+        "calc": "계산 중입니다.",
+        "deleteTip": "해당 데이터 세트의 핫스팟 및 측정 결과도 함께 삭제되며, 이 작업은 취소할 수 없습니다.",
+        "joinTip": "장면을 선택한 후 해당 {dataset}이 현재 장면과 병합됩니다"
     },
     "crop": {
         "pointActions": {
@@ -411,18 +525,24 @@
             "exclude": "상자 안에서 제거",
             "rotate": "회전",
             "clear": "비우기",
-            "move": "이동"
+            "move": "이동",
+            "scale": "확대/축소"
         },
         "resetConfirm": "초기 상태로 복원하려면 재계산이 필요합니다.  \n추가된 핫스팟, 측정, 공간 모델, 자른 효과, 병합/업로드된 데이터셋은 모두 삭제됩니다. 신중하게 진행해 주세요.",
         "reset": "초기 상태로 복원",
         "tipOper": "작업 알림",
         "panoNotAllConnected": "끊어진 점클라우드가 감지되었습니다. 계산할 수 없습니다.",
         "clearConfirm": "모든裁剪框을 비우시겠습니까? 이 작업은 취소할 수 없습니다.",
-        "calcConfirm": "계산을 진행하시겠습니까?\n계산에는 시간이 걸릴 수 있으니,裁剪이 완료된 후에 이 작업을 진행해 주세요."
+        "calcConfirm": "계산을 진행하시겠습니까?\n계산에는 시간이 걸릴 수 있으니,裁剪이 완료된 후에 이 작업을 진행해 주세요.",
+        "title": "점 클라우드 자르기",
+        "tip": "점 클라우드를 자르기 전에 확인해 주세요. ",
+        "needToDisConnect": "한 지점을 선택해 주세요. 그리고 그 지점과 주변 지점들과의 연결을 제거해 주세요",
+        "calcBtn": "재계산"
     },
     "earthwork": {
         "unit": {
-            "meter": "미터"
+            "meter": "미터",
+            "inch": "영국식 단위 (ft)"
         },
         "slamWring": "SLAM 모드에서 촬영한 장면은 당분간 토방량 측정 기능을 지원하지 않습니다.",
         "export": "보고서 다운로드",
@@ -471,7 +591,7 @@
         "titlePlac": "\"추가 메모\"를 클릭하세요.",
         "repeatTitle": "다시 그리시겠습니까?",
         "heightTypes": [
-            null,
+            "사용자 정의 평면",
             "최고점 평면",
             "최저점 평면"
         ],
@@ -490,7 +610,17 @@
         "minHeight": "최소 고도",
         "uncalc": "계산되지 않았습니다.",
         "downIng": "다운로드 중입니다.",
-        "calcConfirmTitle": "계산을 진행하시겠습니까?"
+        "calcConfirmTitle": "계산을 진행하시겠습니까?",
+        "dname": "작도",
+        "calc": "계산",
+        "getHref": "주소를 가져오는 중입니다.",
+        "area": "면적",
+        "len": "길이",
+        "inputAuthor": "보고자 이름을 입력해 주세요",
+        "copy": "링크 복사 성공!",
+        "downFormatDis": "샘플링 거리",
+        "heightSlice": "높이",
+        "calcContent": "이 페이지에서 기다려 주세요. 페이지를 떠나면 계산이 자동으로 취소됩니다"
     },
     "epoint": {
         "closeRTK": "RTK 위치 끄기",
@@ -498,7 +628,9 @@
             "disconnect": "연결 삭제",
             "scale": "확대",
             "rotate": "회전",
-            "move": "이동"
+            "move": "이동",
+            "connect": "링크",
+            "reset": "재설정하다"
         },
         "openRTKTip": "RTK 파라미터를 사용하여 위치를 측정합니다.",
         "closeRTKTip": "현재 위치를 사용하여 위치를 측정합니다.",
@@ -508,13 +640,18 @@
         "editTip": "점클라우드를 선택하지 않은 상태에서 마우스 왼쪽 버튼으로 시점을 회전하고, 오른쪽 버튼으로 시점을 이동할 수 있습니다.",
         "panoNotAllConnected": "끊어진 점클라우드가 감지되었습니다. 계산할 수 없습니다.",
         "calcConfirm": "계산을 진행하시겠습니까?  \n씬은 기본값으로 복원됩니다. 점 클라우드 씬에 추가된 핫스팟, 측정, 공간 모델, 병합/업로드된 데이터셋은 삭제되며, 자른 점 클라우드는 초기 상태로 복원됩니다. 또한, Obj 씬의 모델이 리셋됩니다.",
-        "calcConfirmKanKan": "계산을 진행하시겠습니까?\n씬은 기본 상태로 복원되며, 추가된 3D 모델이 삭제됩니다."
+        "calcConfirmKanKan": "계산을 진행하시겠습니까?\n씬은 기본 상태로 복원되며, 추가된 3D 모델이 삭제됩니다.",
+        "noEnter": "이 장면은 재계산 후에 포인트 보정 기능을 사용할 수 있습니다.",
+        "needToDisConnect": "한 지점을 선택해 주세요. 그리고 그 지점과 주변 지점들과의 연결을 제거해 주세요",
+        "resetConfirmKanKan": "초기화 후 씬은 마지막 계산 완료 상태로 복원됩니다. 초기화하시겠습니까? 이 작업은 취소할 수 없습니다."
     },
     "err": {
         "serve": {
             "desc": [
-                "플랫폼 자원을 더 잘 사용할 수 있도록 업그레이드 중입니다. 업그레이드 중에는 액세스할 수 없습니다."
-            ]
+                "플랫폼 자원을 더 잘 사용할 수 있도록 업그레이드 중입니다. 업그레이드 중에는 액세스할 수 없습니다.",
+                "불편을 끼쳐 드려 죄송합니다."
+            ],
+            "title": "시스템 업그레이드 중"
         },
         "scene": {
             "webgl": "메모리가 부족합니다. 여러 페이지나 프로그램을 동시에 열지 마십시오. 브라우저를 다시 시작한 후 다시 여십시오.",
@@ -525,7 +662,8 @@
             "err": "씬 계산에 실패했습니다. 다시 시도해 주세요."
         },
         "preset": "메모리가 부족합니다. 여러 페이지나 프로그램을 동시에 열지 마십시오. 브라우저를 다시 시작한 후 다시 여십시오.",
-        "sdk": "레이저 씬 열기에 실패했습니다. 브라우저를 닫고 다시 열어 주세요."
+        "sdk": "레이저 씬 열기에 실패했습니다. 브라우저를 닫고 다시 열어 주세요.",
+        "disconnect": "네트워크 오류가 발생했습니다. 다시 시도해 주세요."
     },
     "help": {
         "prev": "이전 단계",
@@ -537,14 +675,16 @@
                 "content": "버튼을 클릭하여 파노라마/포인트 클라우드를 전환합니다."
             },
             "step3": {
-                "content": "두 손가락으로 화면을 밀거나 모아서 확대 또는 축소하세요."
+                "content": "두 손가락으로 화면을 밀거나 모아서 확대 또는 축소하세요.",
+                "title": "확대/축소"
             },
             "step2": {
                 "content": "스크린을 좌우로 슬라이드하세요.",
                 "title": "시점 회전"
             },
             "step1": {
-                "content": "임의의 방향을 클릭하면 이동할 수 있습니다."
+                "content": "임의의 방향을 클릭하면 이동할 수 있습니다.",
+                "title": "걷기"
             }
         },
         "edit": {
@@ -577,6 +717,9 @@
             ],
             "floorpan": [
                 "알고리즘이 자동으로 씬 평면도를 생성하며, 다운로드, 교체 또는 숨기기를 지원합니다."
+            ],
+            "coord": [
+                "귀하의 씬에 RTK 또는 관련 장비를 사용하여 제어점을 수집한 경우, 씬 내의 모든 위치에 대한 지리 좌표를 가져올 수 있으며, 여러 좌표계 변환을 지원합니다."
             ]
         },
         "query": {
@@ -594,7 +737,15 @@
         },
         "title": "초보자 가이드",
         "init": "씬 편집 플랫폼에 오신 것을 환영합니다!",
-        "link": "사용자 매뉴얼"
+        "link": "사용자 매뉴얼",
+        "video": {
+            "spaceModel": "https://docs.4dkankan.com/#/product/laser/zh-cn/createfloor",
+            "epoint": "https://docs.4dkankan.com/#/product/laser/zh-cn/calibrationpoint",
+            "kankanEpoint": "https://docs.4dkankan.com/#/product/laser/zh-cn/calibrationpoint",
+            "spaceDivision": "https://docs.4dkankan.com/#/product/laser/zh-cn/splicing",
+            "coordinate": "https://docs.4dkankan.com/#/product/laser/zh-cn/setcontrolpoint"
+        },
+        "videoBtn": "비디오 튜토리얼"
     },
     "hotspot": {
         "all": "모든 핫스팟",
@@ -606,11 +757,17 @@
             },
             "video": {
                 "place": "동영상 업로드",
-                "desc": "MP4, MOV 비디오 형식을 지원하며, 비디오의 비트레이트는 2Mbps 이하, 파일 크기는 20MB를 초과할 수 없습니다."
+                "desc": "MP4, MOV 비디오 형식을 지원하며, 비디오의 비트레이트는 2Mbps 이하, 파일 크기는 20MB를 초과할 수 없습니다.",
+                "title": "비디오"
             },
             "audio": {
                 "place": "오디오 업로드",
-                "desc": "MP3, WAV 형식을 지원하며, 파일 크기는 5MB를 초과할 수 없습니다."
+                "desc": "MP3, WAV 형식을 지원하며, 파일 크기는 5MB를 초과할 수 없습니다.",
+                "title": "오디오"
+            },
+            "web": {
+                "place": "웹 페이지 표시 영역",
+                "title": "링크"
             }
         },
         "range": {
@@ -620,17 +777,27 @@
         },
         "edit": {
             "unTitle": "핫스팟에 제목이 작성되지 않았습니다.",
-            "addLink": "링크 추가"
+            "addLink": "링크 추가",
+            "maxContentLen": "먼저 공간을 정리한 후 링크를 추가해 주세요!",
+            "placeholder": {
+                "addLinkContent": "링크 주소를 입력해 주세요",
+                "addLinkTitle": "링크 텍스트를 입력해 주세요",
+                "content": "내용을 입력해 주세요",
+                "title": "인기 있는 이슈 제목을 입력해 주십시오"
+            }
         },
         "added": "핫스팟이 추가되었습니다.",
         "name": "핫스팟",
         "show": "핫스팟 표시",
         "addMenu": "핫스팟 추가",
-        "deleteConfirm": "이 {type}을(를) 삭제하시겠습니까?"
+        "deleteConfirm": "이 {type}을(를) 삭제하시겠습니까?",
+        "addTip": "장면에서 마우스 오른쪽 버튼을 클릭하여 '핫스팟 추가'를 선택해 주세요",
+        "flyErr": "거리가 너무 멀어 조작이 실패했습니다"
     },
     "measure": {
         "unit": {
-            "meter": "미터"
+            "meter": "미터",
+            "inch": "영국식 단위 (ft)"
         },
         "stop": "측정 중지",
         "name": "측정하다",
@@ -643,7 +810,10 @@
         "downloadName": "측정 결과",
         "unSave": "측정 결과가 저장되지 않아 공유 링크를 생성할 수 없습니다.",
         "invalidPoint": "점 클라우드가 비어 있어 측정할 수 없습니다.",
-        "titlePlac": "\"추가 메모\"를 클릭하세요."
+        "titlePlac": "\"추가 메모\"를 클릭하세요.",
+        "area": "면적",
+        "len": "길이",
+        "copy": "링크 복사 성공!"
     },
     "record": {
         "all": "모든 비디오",
@@ -658,7 +828,11 @@
         "fileName": "스크린 녹화",
         "desc": "<span>{key}</span>를 눌러 녹화를 일시 정지할 수 있습니다.",
         "showSetting": "보기 설정",
-        "delTip": "이 비디오를 삭제하시겠습니까?"
+        "delTip": "이 비디오를 삭제하시겠습니까?",
+        "jx": "녹화 계속",
+        "defName": "강의 비디오",
+        "nameEmpty": "비디오 이름은 비워둘 수 없습니다.",
+        "rename": "이름 변경"
     },
     "resStatus": {
         "503": "업로드 중 이상이 발생했습니다",
@@ -708,7 +882,22 @@
         "408": "로그인 상태가 만료되어 자동으로 로그아웃되었습니다.",
         "3009": "로그인에 실패했습니다. 잠시 후 다시 시도해 주세요.",
         "loginErr": "로그인에 실패했습니다. 잠시 후 다시 시도해 주세요.",
-        "6008": "알고리즘으로 평면도 생성에 실패했습니다. info.json 파일이 존재하지 않습니다."
+        "6008": "알고리즘으로 평면도 생성에 실패했습니다. info.json 파일이 존재하지 않습니다.",
+        "204": "시스템이 처리 중입니다. 반복해서 제출하지 마세요",
+        "500": "시스템 내부 오류",
+        "305": "해당 계정은 이미 다른 기기에서 로그인되었으며, 본 기기의 계정은 자동으로 로그아웃됩니다.",
+        "402": "접근이 제한되었습니다. 인증이 만료되었습니다.",
+        "2003": "이 장면은 재계산 후에 포인트 보정 기능을 사용할 수 있습니다.",
+        "3015": "해당 사용자가 등록되지 않았습니다",
+        "accountErr": "해당 계정에서 현재 장면이 감지되지 않았습니다. 계정을 변경하고 다시 로그인해 주세요.",
+        "4003": "올바른 지리 좌표를 입력해 주세요",
+        "4001": "인증 코드 전송 오류가 발생했습니다.",
+        "3103": "호출 계산이 실패했습니다",
+        "3021": "계정이 존재하지 않습니다. 확인한 후 다시 입력해 주세요.",
+        "3014": "계정이나 비밀번호가 올바르지 않습니다.",
+        "6001": "변환 모델 오류, 장면을 찾을 수 없습니다",
+        "6002": "변환 모델 오류, 제어점을 찾을 수 없습니다",
+        "6003": "변환 모델 오류, 제어점을 설정하지 않았습니다"
     },
     "scene": {
         "objTip": "망사 프로젝트",
@@ -727,7 +916,9 @@
             "cloud": "포인트 클라우드 다운로드",
             "formatNotSupport": "형식을 지원하지 않습니다.",
             "cloudSuccess": "점 클라우드 다운로드가 완료되었습니다.",
-            "btn": "지금 다운로드"
+            "btn": "지금 다운로드",
+            "cropCloud": "자른 후 다운로드",
+            "nullCloud": "자른 범위 내의 점 클라우드가 비어 있어 다운로드할 수 없습니다."
         },
         "spaceModel": {
             "defaultFloorTitle": "건물",
@@ -736,14 +927,17 @@
         "floorpan": {
             "customize": {
                 "steps": [
-                    null,
+                    "먼저 기본 평면도를 다운로드한 후 수정하거나 교체하여 업로드해 주세요.",
                     "업로드, 원본문서형식으로업로드, 크기를수정하지않습니다."
                 ],
-                "success": "평면도 업데이트가 완료되었습니다."
+                "success": "평면도 업데이트가 완료되었습니다.",
+                "title": "사용자 정의",
+                "un": "{title}의 사용자 정의 이미지를 업로드해 주세요"
             },
             "downsuccess": "평면도 다운로드가 완료되었습니다.",
             "title": "평면도",
-            "un": "평면도가 업로드되지 않았습니다."
+            "un": "평면도가 업로드되지 않았습니다.",
+            "default": "시스템 기본값"
         },
         "pose": {
             "unImage": "전경 모드에서는 위치를 설정할 수 없습니다",
@@ -756,7 +950,11 @@
         "navPath": "네비게이션 경로",
         "cloud": "점 클라우드",
         "cloudTip": "점 클라우드 모드로 전환하려면 클릭하세요.",
-        "getPointError": "빈 영역에서는 점위를 가져올 수 없습니다. 점 클라우드 영역으로 이동한 후 다시 시도해 주세요."
+        "getPointError": "빈 영역에서는 점위를 가져올 수 없습니다. 점 클라우드 영역으로 이동한 후 다시 시도해 주세요.",
+        "flyUnImages": "이 위치에는 파노라마 이미지가 없습니다. 포인트 클라우드 모드로 전환 후 다시 시도해 주세요.",
+        "navPlaceholder": "확인해 주세요",
+        "navErr": "데이터 세트 범위를 벗어나, 길을 계획할 수 없습니다",
+        "invalidRight": "포인트 클라우드 영역으로 이동한 후 다시 시도해 주세요"
     },
     "sys": {
         "repeatPwdDiff": "두 번 입력한 비밀번호가 일치하지 않습니다",
@@ -788,7 +986,8 @@
         "time": {
             "m": "분",
             "h": "시간",
-            "s": "초"
+            "s": "초",
+            "about": "약"
         },
         "setting": {
             "setName": "이름 수정",
@@ -796,7 +995,10 @@
             "setNameErr": "장면 이름은 비워둘 수 없습니다!",
             "pwd": "암호화",
             "setView": "보기 설정",
-            "setOpen": "보기 설정"
+            "setOpen": "보기 설정",
+            "setPic": "초기 화면 설정",
+            "setNamePlace": "제목을 입력해 주세요",
+            "setOpenErr": "암호화 비밀번호를 입력해 주세요!"
         },
         "cancel": "취소하다",
         "ok": "알겠습니다",
@@ -827,7 +1029,9 @@
         "dialogTitle": "알림",
         "unData": "현재 데이터가 없습니다.",
         "repeatLogin": {
-            "title": "다른 장치에서 이미 해당 계정에 로그인한 것이 감지되었습니다. 계속 진행하시겠습니까?"
+            "title": "다른 장치에서 이미 해당 계정에 로그인한 것이 감지되었습니다. 계속 진행하시겠습니까?",
+            "btn": "계속 로그인",
+            "content": "【계속 로그인】을 선택하면 다른 기기에서 로그아웃되고 작업이 저장되지 않습니다."
         },
         "submit": "제출",
         "show": "표시",
@@ -842,7 +1046,26 @@
         "qrLoginTitle": "카메라 로그인",
         "unRepeatPwd": "확인 비밀번호는 비워둘 수 없습니다.",
         "logoutConfirm": "로그아웃하시겠습니까?",
-        "resetConfirm": "초기화하시겠습니까? 이 작업은 취소할 수 없습니다."
+        "resetConfirm": "초기화하시겠습니까? 이 작업은 취소할 수 없습니다.",
+        "getCode": "인증 코드를 받기",
+        "markPwd": "비밀번호 기억하기",
+        "setPwdPlace": "비밀번호 설정",
+        "calc": "계산",
+        "setup": "설정",
+        "uploadAddText": "추가 계속",
+        "detail": "상세 정보",
+        "phonePlace": "휴대폰 번호를 입력해 주세요.",
+        "inputScenePwd": "장면 비밀번호를 입력하세요",
+        "pwdPlace": "비밀번호를 입력해 주세요",
+        "codePlace": "인증 코드를 입력해 주세요",
+        "setRepeatPwdPlace": "비밀번호 확인",
+        "inputPlc": "입력해 주세요",
+        "selectPic": "선택해 주세요",
+        "logout": "나가다",
+        "leave": "나가다",
+        "hide": "숨기기",
+        "reset": "재설정하다",
+        "downloadWXSuccess": "브라우저로 열어서 다시 시도해 주세요"
     },
     "sceneDetail": {
         "title": "씬 데이터"
@@ -854,7 +1077,9 @@
         "addScene": "씬 추가",
         "yc": "제거",
         "delTops": "삭제하시겠습니까?",
-        "ycTips": "현재 씬을 제거하시겠습니까?"
+        "ycTips": "현재 씬을 제거하시겠습니까?",
+        "sceneName": "장면 이름",
+        "caseView": "케이스 미리보기"
     },
     "mediaLibrary": {
         "Modeling": "경사 촬영",
@@ -870,7 +1095,9 @@
             "0": "전체",
             "1": "이미지",
             "5": "기타",
-            "4": "모델"
+            "4": "모델",
+            "2": "비디오",
+            "3": "오디오"
         },
         "dictName": "그룹",
         "tips": {
@@ -885,7 +1112,10 @@
             "operate": "작업이 성공적으로 완료되었습니다.",
             "add": "추가 성공!",
             "uplooadfiletype": "JPG, PNG, JPEG, MP4, WAV, MP3, SHP, ZIP 형식의 파일 업로드를 지원합니다.",
-            "deltext": "삭제하시겠습니까? 이 작업은 취소할 수 없습니다."
+            "deltext": "삭제하시겠습니까? 이 작업은 취소할 수 없습니다.",
+            "dictId": "그룹을 선택해 주세요",
+            "placeholderName": "이름을 입력해 주세요",
+            "noName": "이름을 입력해 주세요"
         },
         "addFolder": "새 폴더 만들기",
         "add": "파일 추가",
@@ -896,7 +1126,10 @@
         "fileTypeStr": "파일 유형",
         "operate": "작업",
         "fileFormat": "파일 형식",
-        "statusStr": "상태"
+        "statusStr": "상태",
+        "addFolderName": "폴더 이름을 입력해 주세요",
+        "addFilePlace": "파일 이름을 입력해 주세요",
+        "addFolderPlace": "폴더 이름을 입력해 주세요"
     },
     "fire": {
         "effect": {
@@ -923,6 +1156,8 @@
         "rotate": "회전",
         "lfree": "수평",
         "rect": "직사각형",
-        "move": "이동"
+        "move": "이동",
+        "free": "자유",
+        "series": "연속 직선"
     }
 }

+ 16 - 5
src/i18n/weblate/zh.json

@@ -167,7 +167,7 @@
             "edit": "编辑",
             "all": "全选",
             "coverStatus": {
-                "un": "计算",
+                "un": "计算",
                 "ing": "计算中",
                 "err": "计算失败",
                 "pause": "计算暂停",
@@ -349,7 +349,12 @@
             "exporting": "导入中",
             "packingFailure": "打包失败",
             "unPacking": "未打包",
-            "unGenerate": "未生成"
+            "unGenerate": "未生成",
+            "firstPacking": "打包",
+            "import": "导入",
+            "updateAt": "更新于",
+            "exportingOut": "导出中",
+            "isCopyExist": "场景已存在,是否保存为副本?"
         },
         "multiLang": "多语言",
         "migrateScene": {
@@ -368,9 +373,13 @@
             "status_0": "计算中",
             "status_4": "重算中",
             "status_3": "封存",
-            "updateTime": "更新时间"
+            "updateTime": "更新时间",
+            "miss_case_title": "请填写案件名称!",
+            "miss_case": "请选择场景!",
+            "add_case": "新增案件"
         },
-        "calcFailureTip": "确定重算?部分此操作无法撤销。 注意:重算点云场景会清空部分数据如合并的数据集、热点等,请谨慎操作。"
+        "calcFailureTip": "确定重算?部分此操作无法撤销。 注意:重算点云场景会清空部分数据如合并的数据集、热点等,请谨慎操作。",
+        "path_no_exist": "该路径不存在。"
     },
     "crop": {
         "title": "裁剪点云",
@@ -1106,7 +1115,9 @@
         "addScene": "添加场景",
         "delTops": "确定删除?",
         "ycTips": "确定要移除当前场景吗?",
-        "sceneName": "场景名称"
+        "sceneName": "场景名称",
+        "yctips": "无法移除,场景已加入多元融合,请进入多元融合删除场景后再试",
+        "yctipsErr": "无法移除,场景已加入多元融合,请进入多元融合删除场景后再试"
     },
     "mediaLibrary": {
         "title": "媒体库",

+ 4 - 1
src/request/config.ts

@@ -24,6 +24,7 @@ import {
   userLogin,
   userReg,
   newFileupload,
+  uploadUrl,
 } from "./urls";
 
 // 不需要登录就能请求的接口
@@ -38,7 +39,8 @@ export const notLoginUrls = [
   getAttachListByPsw,
 ];
 // 需要用表单提交的数据
-export const fromUrls: string[] = [];
+export const fromUrls: string[] = [
+];
 // 带文件的请求
 export const fileUrls = [
   uploadAttachFile,
@@ -50,6 +52,7 @@ export const fileUrls = [
   saveCaseFileInfo,
   newFileupload,
   ffmpegMergeImage,
+  uploadUrl,
 ];
 // 需要限定卫GET请求方式的url
 export const GetUrls = [getRoleList, getCompanyList];

+ 15 - 4
src/request/index.ts

@@ -3,6 +3,7 @@ import { ElMessage, ElMessageBox } from "element-plus";
 import qs from "qs";
 import { openLoading, closeLoading } from "./loading";
 import { openErrorMsg } from "./errorMsg";
+import { ui18n } from '@/i18n'
 import {
   fromUrls,
   fileUrls,
@@ -66,15 +67,18 @@ axios.interceptors.request.use(async (config) => {
       config.data = config.params;
     }
   }
-
   // 处理需要用表单上传的请求
   if (~fromUrls.indexOf(config.url)) {
     config.data = qs.stringify(config.data);
     config.headers["Content-Type"] =
       "application/x-www-form-urlencoded; charset=utf-8;";
   } else if (~fileUrls.indexOf(config.url)) {
+    config.headers["Content-Type"] = "multipart/form-data";
+    // if(Object.prototype.toString.call(config.data) === '[object Object]'){
+    //   config.data = config.data;
+    //   return config;
+    // }
     const fromData = new FormData();
-
     Object.keys(config.data).forEach((key) => {
       if (key === "files") {
         Array.from(config.data[key]).forEach((file) => {
@@ -85,15 +89,20 @@ axios.interceptors.request.use(async (config) => {
       }
     });
     config.data = fromData;
-    config.headers["Content-Type"] = "multipart/form-data";
   }
-
+  if(config.noLoading){
+    return config;
+  }
   openLoading(config.url);
   return config;
 });
 
 const responseInterceptor = (res: AxiosResponse<any, any>) => {
   closeLoading();
+  if(res.data.code == '8030'){
+    openErrorMsg(ui18n.t('sceneHome.yctipsErr'));
+    throw res.data.msg;
+  }
   // if (res.data.code === 4010){
   //   ElMessageBox.alert("您没有访问权限", "提示", {
   //     confirmButtonText: "我知道了",
@@ -125,7 +134,9 @@ const responseInterceptor = (res: AxiosResponse<any, any>) => {
 
 axios.interceptors.response.use(responseInterceptor, (error) => {
   closeLoading();
+  console.log("请求失败", error.response);
   if (error.response && error.response.data) {
+    // error.response.code == '8030' && openErrorMsg(ui18n.t('sceneHome.yctiyctipsErrps'));
     return responseInterceptor(error.response);
   } else {
     openErrorMsg(

+ 9 - 9
src/request/urls.ts

@@ -12,7 +12,7 @@ export const userLogin = "/fusion/fdLogin";
 // 权限
 export const userperInfo = "/fusion/web/user/getPerInfo";
 // export const userInfo = "/fusion/web/user/getUserInfo";
-export const userInfo = "/service/manage/sysUser/getInfo";
+export const userInfo = "/fusion/sysUser/getInfo";
 // 注册
 export const userReg = "/web/user/register";
 // 发送注册短信
@@ -106,7 +106,7 @@ export const isdyrh = "/fusion/caseFusion/list";
 export const caseSceneList = `/fusion/case/sceneList`;
 export const repCaseScenes = `/fusion/case/addScene`;
 export const syncInfo = `/fusion/caseLive/getTakeLookRoom`;
-export const sceneListHasAi = '/service/manage/case/sceneListHasAi'
+export const sceneListHasAi = '/fusion/case/sceneListHasAi'
 // 获取caseTaggings
 export const caseTaggingList = `/fusion/caseTag/allList`;
 
@@ -178,7 +178,7 @@ export const saveCaseFileInfo = "/fusion/caseFiles/addOrUpdateImg";
 export const insertCaseFile = "/fusion/caseFiles/add";
 export const deleteCaseFile = "/fusion/caseFiles/delete";
 export const updateCaseFile = "/fusion/caseFiles/updateTitle";
-export const newFileupload = "/service/manage/common/upload/fileNew";
+export const newFileupload = "/fusion/upload/fileNew";
 //勘验笔录信息
 export const caseInquestInfoOld = "/fusion/caseInquestCriminal/info";
 export const caseInquestOpt = "/fusion/caseInquestCriminal/saveOrUpdate";
@@ -238,8 +238,8 @@ export const getSysSetting = `/fusion/systemSetting/info`;
 export const updateSysSetting = `/fusion/systemSetting/save`;
 
 // 案件相关接口
-export const sceneList = "/service/manage/case/sceneList"; //{roomId}
-export const getCaseByNum = "/service/manage/case/getCaseByNum"; //{roomId}
+export const sceneList = "/fusion/case/sceneList"; //{roomId}
+export const getCaseByNum = "/fusion/case/getCaseByNum"; //{roomId}
 export const newCaseInfo = "/fusion/caseInquestInfo/info";
 export const casesaveOrUpdate = "/fusion/caseInquestInfo/saveOrUpdate";
 export const getByTree = "/fusion/caseFilesType/getByTree";
@@ -251,10 +251,10 @@ export const criminalInfo = "/fusion/caseInquestCriminal/info";
 export const saveOrUpdate = "/fusion/caseInquestCriminal/saveOrUpdate";
 export const downDocx = "/fusion/caseInquestCriminal/downDocx";
 export const getSceneList = "/fusion/scene/list";
-export const getDictFileList = "/service/manage/dict/getByKey/media-library";
-export const delDictFileList = "/service/manage/dictFile/del/media-library";
-export const getListFileList = "/service/manage/dictFile/pageList/media-library";
-export const caseaddOrUpdate = '/service/manage/case/addOrUpdate';
+export const getDictFileList = "/fusion/dict/getByKey/media-library";
+export const delDictFileList = "/fusion/dictFile/del/media-library";
+export const getListFileList = "/fusion/dictFile/pageList/media-library";
+export const caseaddOrUpdate = '/fusion/case/addOrUpdate';
 //地图相关
 // export const getTips = "http://map.jms.gd//s/api/gettips";
 // export const getTipsName = "http://map.jms.gd/s/api/gettips_name";

+ 6 - 6
src/router/config.ts

@@ -79,12 +79,12 @@ export const routes: Routes = [
         component: () => import("@/view/other/index.vue"),
         meta: { title: "其他材料", icon: "icon-fuzhi" },
       },
-      {
-        name: RouteName.aiList,
-        path: "aiList/:caseId",
-        component: () => import("@/view/aiList/index.vue"),
-        meta: { title: "AI 勘查", icon: "icon-chaxun" },
-      },
+      // {
+      //   name: RouteName.aiList,
+      //   path: "aiList/:caseId",
+      //   component: () => import("@/view/aiList/index.vue"),
+      //   meta: { title: "AI 勘查", icon: "icon-chaxun" },
+      // },
       {
         name: RouteName.dossier,
         path: "dossier/:caseId",

+ 49 - 37
src/store/case.ts

@@ -97,7 +97,7 @@ export const getfzdel = async (params) =>
 export const getmediaList = async (params) =>
   (await axios.post<string>(mediaList, params)).data;
 
-export const addByMediaLiBrary = async (params) =>{
+export const addByMediaLiBrary = async (params) => {
   const newUrl = params.uploadIds?.length ? addByMediaLibrarys : addByMediaLibrary
   return (await axios.post<string>(newUrl, params)).data;
 }
@@ -106,18 +106,18 @@ export const AddsaveOrUpdate = async (params) =>
   (await axios.post<string>(saveOrUpdate, params)).data;
 
 export const getcaseInDate = async (params) =>
-  (await axios.get<string>(criminalInfo, {params})).data;
-  // (await axios.get<string>(info, { params:params })).data;
+  (await axios.get<string>(criminalInfo, { params })).data;
+// (await axios.get<string>(info, { params:params })).data;
 
 export const getSceneListData = async (params) =>
   (await axios.get<string>(getSceneList, { params })).data;
-  // (await axios.get<string>(info, { params:params })).data;
+// (await axios.get<string>(info, { params:params })).data;
 
 export const getCaseInfo = async (caseId) => {
-  caseInfoData.value =  (await axios.get<Case>(caseInfo, { params: { caseId } })).data
+  caseInfoData.value = (await axios.get<Case>(caseInfo, { params: { caseId } })).data
   return caseInfoData.value;
 }
-  
+
 
 export const getCaseInquestInfo = async (caseId: number) =>
   (await axios.get(newCaseInfo, { params: { caseId } })).data;
@@ -158,7 +158,7 @@ export const getcaseLists = async (caseId: number): Promise<Scene[]> => {
 };
 
 export const updateByTreeFileLists = async (caseId = router.currentRoute.value?.params?.caseId): Promise<Scene[]> => {
-  let list = (await axios.get(getByTree, { params: { caseId:caseId } })).data
+  let list = (await axios.get(getByTree, { params: { caseId: caseId } })).data
   // function getTreeList(lists: any[]) {
   //   return lists.map(item => {
   //     return {
@@ -203,8 +203,17 @@ export const caseUpdateSort = (list: [CaseImg]) =>
 export const saveOrAndSave = (params) =>
   axios.post(uploadImagesAndSave, { ...params });
 
-export const uploadFile = async (params) => {
-  return (await axios.post<string>(uploadUrl, params)).data;
+export const uploadFile = async (params, progressCallback) => {
+  return axios<undefined>({
+    method: "POST",
+    url: uploadUrl,
+    data: params,
+    noLoading: true,
+    onUploadProgress(event: any) {
+      progressCallback(Math.round((event.loaded / event.total) * 100) || 0);
+    },
+  })
+  // return (await axios.post<string>(uploadUrl, params, {noLoading: true} )).data;
 };
 
 export const getByTreeFileLists = async () => {
@@ -256,7 +265,7 @@ export const replaceCaseScenes = (caseId: number, caseScenes: CaseScenes) =>
   axios.post(repCaseScenes, { sceneNumParam: caseScenes, caseId });
 
 export const uploadNewFile = (data) => {
-  console.log('uploadNewFile',data);
+  console.log('uploadNewFile', data);
   return axios<undefined>({
     method: "POST",
     url: newFileupload,
@@ -288,11 +297,13 @@ export const getSceneListHasAi = (caseId: number) =>
   axios.get(sceneListHasAi, { params: { caseId } });
 
 export const getTipsList = (key) =>
-  axios.get(getTips, { params: { 
-    basic: 'y',
-    key,
-    location: '113.05,22.61',
-   } });
+  axios.get(getTips, {
+    params: {
+      basic: 'y',
+      key,
+      location: '113.05,22.61',
+    }
+  });
 
 export const getTipsNames = (name) =>
   axios.get(getTipsName, { params: { name } });
@@ -301,7 +312,7 @@ export const submitMergePhotos = (data) => axios.post(ffmpegMergeImage, { ...dat
 
 export const getCaseInfoData = () => caseInfoData.value
 export const getCaseSceneListData = (caseId) => {
-  if(sceneList.value && sceneList.value.length == 0 && !isloadList) return getCaseSceneList(caseId).then(res => {
+  if (sceneList.value && sceneList.value.length == 0 && !isloadList) return getCaseSceneList(caseId).then(res => {
     isloadList = true
     sceneList.value = res
     return sceneList.value
@@ -315,50 +326,51 @@ export const getSceneListTree = (list = sceneList.value) => {
     1: [],//mesh
   };
   list && list.map((item) => {
-    if(item.type == 2 || item.type == 5 ) {
+    if (item.type == 2 || item.type == 5) {
       myData[0].push(item.num)
-    }else{
+    } else {
       myData[1].push(item.num)
     }
   });
-  return [{numList:myData[0],type: 0}, {numList:myData[1],type: 1}]
+  return [{ numList: myData[0], type: 0 }, { numList: myData[1], type: 1 }]
 }
 
 export const getUrlSrc = (item, caseId) => {
+  // 初始化参数对象
   let param = {
-    root:'',
-    ossRoot:'',
-    serviceUrl:'',
-    laserRoot:'',
-    swssUrl:'',
-    swkkUrl:'',
+    root: '',
+    ossRoot: '',
+    serviceUrl: '',
+    laserRoot: '',
+    swssUrl: '',
+    swkkUrl: '',
     swssmxUrl: '',
     fuse: '',
   }
   let params = getUrlData();
   let langKey = params.lang || "zh";
   const ip = params.ip ? params.ip + ":" : "";
-  param.root = `${ip}${params.laserServicePort}`;
-  param.ossRoot = `${ip}${params.laserServicePort}${params.static}`;
-  param.serviceUrl = `${ip}${params.servicePort}/fusion`;
+  param.root = `${ip}:${params.laserServicePort}`;
+  param.ossRoot = `${ip}:${params.laserServicePort}${params.static}`;
+  param.serviceUrl = `${ip}:${params.servicePort}/fusion`;
   param.laserRoot = param.root;
 
   param.swssUrl = `${params.swssUrl}?lang=${langKey}&serve_link=${param.laserRoot}&basePath=${param.laserRoot}&m=${item.num}`;
   param.swssmxUrl = `${params.swssUrl}?lang=${langKey}&serve_link=${param.laserRoot}&basePath=${param.laserRoot}&m=${item.num}`;
-  param.swkkUrl = `${params.swkkUrl}?lang=${langKey}&app_server=${params.ip}${params.swkkPort}&m=${item.num}`;
-  param.fuse = `${params.fuse}?caseId=${caseId}&single&ip=${params.ip}&swkkPort=${params.swkkPort}&swssUrl=${params.swssUrl}&swkkUrl=${params.swkkUrl}&laserServicePort=${params.laserServicePort}&servicePort=${params.servicePort}&lang=${langKey}&app_server=${ip}${params.swkkPort}&testMap=1&static=&`;
+  param.swkkUrl = `${params.swkkUrl}?lang=${langKey}&app_server=${params.ip}:${params.swkkPort}&m=${item.num}`;
+  param.fuse = `${params.fuse}?caseId=${caseId}&ip=${params.ip}&swkkPort=${params.swkkPort}&swssUrl=${params.swssUrl }&swkkUrl=${params.swkkUrl.replace('epg','spg')}&laserServicePort=${params.laserServicePort}&servicePort=${params.servicePort}&lang=${langKey}&app_server=${ip}${params.swkkPort}&testMap=1&static=${params.static||''}&`;
   let SceneType = {
-    0: params.swkkUrl,//`/spg.html?m=${item.num}`,
-    1: params.swkkUrl,//`/spg.html?m=${item.num}`,
+    0: param.swkkUrl,//`/spg.html?m=${item.num}`,
+    1: param.swkkUrl,//`/spg.html?m=${item.num}`,
     2: param.swssmxUrl,//`/mega/index.html?m=${item.num}`,
-    3: params.swkkUrl,//`/swss/index.html?m=${item.num}`,
-    4: params.swkkUrl,//`/spg.html?m=${item.num}`,
+    3: param.swkkUrl,//`/swss/index.html?m=${item.num}`,
+    4: param.swkkUrl,//`/spg.html?m=${item.num}`,
     5: param.swssmxUrl,//`/mega/index.html?m=${item.num}`,
-    6: params.swkkUrl,//`/spg.html?m=${item.num}`,
-    7: params.swkkUrl,//`/spg.html?m=${item.num}`,
+    6: param.swkkUrl,//`/spg.html?m=${item.num}`,
+    7: param.swkkUrl,//`/spg.html?m=${item.num}`,
     99: param.fuse + '#/show/summary',//多元融合查看页面`/code/index.html?caseId=${caseId}&single#/show`,
     100: param.fuse + '#/fuseEdit/merge',//多元融合编辑页面`/code/index.html?caseId=${caseId}&single#/show`,
-    101: param.fuse +`title=${item.fileName}&type=${item.fileFormat}&fileUrl=${item.fileUrl}`+ '#/sign-model',//多元融合模型查看页面`/code/index.html?caseId=${caseId}&single#/show`,
+    101: param.fuse + `title=${item.fileName}&single&type=${item.fileFormat}&fileUrl=${item.fileUrl}` + '#/sign-model',//多元融合模型查看页面`/code/index.html?caseId=${caseId}&single#/show`,
     102: param.root,//资源访问链接,
   };
   return SceneType[item.type];

+ 2 - 0
src/store/scene.ts

@@ -56,6 +56,8 @@ export enum QuoteSceneStatus {
   SUCCESS = 2,
   ARCHIVE = 3,
   RERUN = 4,
+  ING = 5,
+  LINEUP = 6,
 }
 
 export interface ModelScene extends BaseScene {

+ 20 - 8
src/store/user.ts

@@ -68,20 +68,32 @@ export const user = ref({
   token: localStorage.getItem("token") || "",
   info: getLocal("info", {} as UserInfo),
 });
-
+const getAllParams = () => {
+  let href = window.location.href;
+  let query = href.substring(href.indexOf("?") + 1);
+  let vars = query.split("&");
+  let obj = {};
+  for (let i = 0; i < vars.length; i++) {
+    let pair = vars[i].split("=");
+    // 将参数名和参数值分别作为对象的属性名和属性值
+    obj[pair[0]] = pair[1];
+  }
+  return obj;
+};
+let querys = getAllParams() || {};
 export const urlData = ref<urlData>({
-  ga: false,
-  lang: "zh",
+  ga: querys.ga || false,
+  lang: querys.lang || "zh",
   photography: '0',
   modeling: '0',
-  appServer: localStorage.getItem('appServer')||'http://192.168.0.62:8808',
-  ip: 'http://192.168.0.62',
-  laserServicePort: '9008',
-  servicePort: '9250',
+  appServer: querys.appServer || localStorage.getItem('appServer')||'http://localhost/:8808',
+  ip: querys.ip ||'http://localhost/',
+  laserServicePort: querys.laserServicePort || '9008',
+  servicePort: querys.laserServiceservicePortPort ||'9250',
   swssUrl: '',
   fuse: decodeURIComponent('file%3A%2F%2F%2FD%3A%2F4DMega%2Fbin%2Fresources%2Fstatic%2Flib%2Ffuse%2Findex.html'),
   swkkUrl: './index.html',
-  swkkPort: '9150',
+  swkkPort: querys.swkkPort || '9008',
   // static: '',
   testMap: '',
 });

+ 2 - 0
src/view/abstract/index.vue

@@ -231,6 +231,7 @@ import { Search } from "@element-plus/icons-vue";
 import { ElMessage, CascaderOption, CascaderProps } from "element-plus";
 import { Example, setExample, getExamplePagging } from "@/app/criminal/store/example";
 import { selectMapImage } from "@/view/case/quisk";
+import { windowOpen } from "@/util";
 import { geoData } from "./getGeo";
 import { getSceneListData, getCaseInquestInfo, casesaveOrUpDate, getcaseInDate, getCaseInfo  } from "@/store/case";
 const selectSetting = ref<CascaderProps>({
@@ -343,6 +344,7 @@ const submit = async () => {
   }else{
     await casesaveOrUpDate({...ruleForm.value, caseId :caseId.value })
   }
+  windowOpen({type: 'fresh', data: '页面刷新' })
   ElMessage.success("保存成功");
 };
 const submitForm = async (formEl) => {

+ 9 - 3
src/view/aiList/index.vue

@@ -24,6 +24,8 @@ import { useUpload } from "@/hook/upload";
 import { RouteName, router } from "@/router";
 import { getSceneListHasAi } from "@/store/case";
 import { Delete, Edit } from "@element-plus/icons-vue";
+import { getUrlData } from "@/store/user";
+import { windowOpen } from "@/util";
 const srcList = [
   "https://07akioni.oss-cn-beijing.aliyuncs.com/07akioni.jpeg",
   "https://fuss10.elemecdn.com/a/3f/3302e58f9a181d2509f3dc0fa68b0jpeg.jpeg",
@@ -40,17 +42,21 @@ const { size, fileList, upload, removeFile, previewFile, file, accept } =
     formats: [".doc", ".docx", , ".pdf"],
   });
 const handleItem = (item) => {
-  window.open(`/spg.html?m=${item.num}&ai=floorplan`)
+  let url = getUrlData();
   active.value = item;
+  windowOpen({
+    url: `${url.swkkUrl}?m=${item.num}&ai=floorplan`,
+    title: item.title,
+  })
 };
 const getList = async () => {
   getSceneListHasAi(caseId.value).then((res) => {
     let newradioList = []
-    res.data.map(ele => {
+    console.log("getSceneListData", res);
+    res.data?.map(ele => {
         newradioList.push(ele)
     })
     list.value = newradioList;
-    console.log("getSceneListData", res);
   });
 };
 getList();

+ 11 - 3
src/view/case/caseFile.vue

@@ -80,7 +80,8 @@ import {
 } from "@/store/caseFile";
 import { getCaseInfo, updateCaseInfo } from "@/store/case";
 import { appConstant } from "@/app";
-import { ElIcon, ElInput, ElMessage } from "element-plus";
+import { ElIcon, ElInput, ElMessage, ElMessageBox } from "element-plus";
+import { ui18n } from '@/i18n'
 
 const caseId = computed(() => {
   const caseId = router.currentRoute.value.params.caseId;
@@ -117,10 +118,17 @@ watchEffect(() => caseId.value && currentTypeId.value && refresh());
 
 const query = (file: CaseFile) => window.open(file.filesUrl + "?time=" + Date.now());
 const del = async (file: CaseFile) => {
-  if (await confirm("确定要删除此数据?")) {
+  ElMessageBox.confirm('确定要删除此数据?', ui18n.t('sys.dialogTitle'), {
+    confirmButtonText: ui18n.t('sys.enter'),
+    cancelButtonText: ui18n.t('sys.cancel'),
+  }).then(async () => {
     await delCaseFile({ caseId: caseId.value!, filesId: file.filesId });
     refresh();
-  }
+  });
+  // if (await confirm("确定要删除此数据?")) {
+  //   await delCaseFile({ caseId: caseId.value!, filesId: file.filesId });
+  //   refresh();
+  // }
 };
 
 const addCaseFileHandler = async () => {

+ 10 - 2
src/view/case/draw/board/editCAD/Service/FloorplanService.js

@@ -1,6 +1,7 @@
 import { floorplanData } from '../FloorplanData'
 import { coordinate } from '../Coordinate.js'
 import Constant from '../Constant'
+import { getUrlSrc } from "@/store/case";
 
 import Title from '../Geometry/Title.js'
 import BgImage from '../Geometry/BgImage.js'
@@ -510,11 +511,18 @@ export class FloorplanService {
         }
         return floorplanData.floors[floor].compass
     }
-
+    isUrl(string){
+        try {
+          new URL(string);
+          return true;
+        } catch (err) {
+          return false;
+        }
+      }
     async loadImageData(src){
         const imageData = await new Promise((resolve, reject) => {
             var img = new Image()
-            img.src = src;
+            img.src = this.isUrl(src)?src:(getUrlSrc({type: 102}) + '/' + src);
             img.crossOrigin=""
             img.onload = function () {
                 resolve(img)

+ 2 - 1
src/view/case/draw/board/useBoard.ts

@@ -69,11 +69,11 @@ const getStore = async (caseId: number, fileId: number, type: BoardType) => {
   } else {
     const fileInfo = await getCaseFileImageInfo(fileId);
     if (fileInfo) {
-      console.log(fileInfo);
       data = {
         ...fileInfo.content,
         id: fileInfo.filesId,
       };
+      console.log("fileInfo", data, fileInfo.content);
     } else {
       router.replace({ name: RouteName.caseFile, params: { caseId } });
       throw "该图不存在!";
@@ -119,6 +119,7 @@ export const useBoard = (props: Ref<BoardProps | null>) => {
       props.value.type
     );
     const boardRaw = (board.value = await boardFactory(store, props.value.dom));
+    console.log("watchEffect", props, boardRaw);
     state.value.exixtsBgImage = !!(store as any).floors[0].bgImage
     onCleanup(() => {
       boardRaw && boardRaw.destroy();

+ 6 - 4
src/view/case/draw/index.vue

@@ -49,7 +49,7 @@ import { selectFuseImage, selectMapImage } from "@/view/case/quisk";
 import { CaseTagging } from "@/store/caseTagging";
 import saveAs from "@/util/file-serve";
 import { BoardTypeDesc } from "@/constant/caseFile";
-import { addByMediaLiBrary, updateByTreeFileLists, uploadNewFile } from "@/store/case";
+import { addByMediaLiBrary, updateByTreeFileLists, uploadNewFile, getUrlSrc } from "@/store/case";
 import { imageCropper } from "@/view/system/quisk";
 import {
   BoardType,
@@ -94,7 +94,8 @@ function getList() {
 getList()
 const backPageHandler = () => {
   board.value && board.value.clear();
-  router.back();
+  router.replace({ name: RouteName.material, params: { caseId: props.caseId } });
+  // router.back();
 };
 
 const setBackImage = (blob: Blob) => {
@@ -103,7 +104,8 @@ const setBackImage = (blob: Blob) => {
 
 const updateAddShape = async (s, d) => {
   if (d) {
-    state.value.addData = await uploadFile(d);
+    state.value.addData = getUrlSrc({type: 102}) + '/' + await uploadFile(d);
+    console.log('state.value', state.value.addData);
   }
   state.value.addShape = s;
 };
@@ -126,8 +128,8 @@ const trackImage = async () => {
     }
   }
 };
-
 const { board, state } = useBoard(props);
+console.log('useBoard', board, state)
 
 // 获取通用数据
 const getStore = async () => {

+ 2 - 3
src/view/case/draw/selectFuseImage.vue

@@ -60,9 +60,8 @@ import { QuiskExpose } from "@/helper/mount";
 
 export type FuseImage = { blob: Blob | null; taggings: CaseTagging[] };
 const props = defineProps<{ caseId: number }>();
-
-const fuseUrl = 'http://192.168.0.25/code/index.html?caseId=528&app=2&share=1&single=1#show/summary'
-// computed(() => getQuery(props.caseId, true, true));
+console.log('fuseUrl', props);
+const fuseUrl =  computed(() => getQuery(props.caseId, true, true));
 
 const taggings = ref<CaseTagging[]>([]);
 const transferSource = computed(() =>

+ 1 - 1
src/view/case/draw/selectMapImage.vue

@@ -71,7 +71,7 @@ const caseId = computed(() => {
 onMounted(async () => {
   caseInfoData.value = await getCaseInfo(caseId.value);
   let center = [22.61, 113.05];
-  if(caseInfoData.value.latAndLong){
+  if(caseInfoData.value?.latAndLong){
     center = caseInfoData.value.latAndLong.split(",")
   }
   console.log("caseInfoData", caseInfoData.value.latAndLong, center);

+ 129 - 48
src/view/case/draw/selectMapImage copy.vue

@@ -1,12 +1,21 @@
 <template>
   <div class="search-layout">
-    <el-input v-model="keyword" placeholder="输入名称搜索" style="width: 350px" clearable>
+    <el-input
+      v-model="keyword"
+      placeholder="输入名称搜索"
+      style="width: 350px"
+      clearable
+    >
       <template #append>
         <el-button :icon="Search" />
       </template>
     </el-input>
     <div class="rrr">
-      <div class="search-result" v-show="keyword && showSearch" ref="resultEl"></div>
+      <div
+        class="search-result"
+        v-show="keyword && showSearch"
+        ref="resultEl"
+      ></div>
       <div class="search-sh" v-show="keyword">
         <el-button style="width: 100%" @click="showSearch = !showSearch">
           {{ showSearch ? "收起" : "展开" }}搜索结果
@@ -15,7 +24,15 @@
     </div>
   </div>
   <div class="def-select-map-layout">
-    <div class="def-select-map" ref="mapEl"></div>
+    <GoogleMap v-if="mapType=='google'"
+    :api-key="ggMapKey"
+    class="def-select-map"
+    :center="center"
+    :zoom="15"
+    >
+        <Marker :options="{ position: center }" />
+    </GoogleMap>
+    <div v-else class="def-select-map" ref="mapEl"></div>
   </div>
 
   <div class="def-map-info" v-if="info">
@@ -28,19 +45,35 @@
 <script setup lang="ts">
 import AMapLoader from "@amap/amap-jsapi-loader";
 import { Search } from "@element-plus/icons-vue";
-import { ref, watchEffect, onMounted  } from "vue";
+import { ref, watchEffect, onMounted, computed } from "vue";
+import { GoogleMap, Marker } from 'vue3-google-map'
+import { getTipsList, getTipsNames, getCaseInfo } from "@/store/case";
 import { QuiskExpose } from "@/helper/mount";
 import { debounce } from "@/util";
-import html2canvas from "html2canvas"
-import L from 'leaflet'
-import 'leaflet/dist/leaflet.css'
+import html2canvas from "html2canvas";
+import { router } from "@/router";
+import L from "leaflet";
+import "leaflet/dist/leaflet.css";
 export type MapImage = { blob: Blob | null; search: MapInfo | null };
 type MapInfo = { lat: number; lng: number; zoom: number; text: string };
 
 const keyword = ref("");
+const ggMapKey = ref("AIzaSyBGUvCR1bppO9pfuS0MUWzuftiZ127y4Os");
 const showSearch = ref(true);
 const info = ref<MapInfo>();
 const searchInfo = ref<MapInfo>();
+const caseInfoData = ref<any>(null);
+const mapType = ref("gaode");
+const center = ref({
+  lat: 113.0,
+  lng: 22.61,
+});
+const caseId = computed(() => {
+  const caseId = router.currentRoute.value.params.caseId;
+  if (caseId) {
+    return Number(caseId);
+  }
+});
 
 watchEffect(() => {
   if (keyword.value) {
@@ -52,21 +85,19 @@ const mapEl = ref<HTMLDivElement>();
 const resultEl = ref<HTMLDivElement>();
 const searchAMap = ref<any>();
 
-watchEffect(async (onCleanup) => {
-  if (!mapEl.value || !resultEl.value) {
-    return;
-  }
+const renderMapGd = async () => {
   const AMap = await AMapLoader.load({
     plugins: ["AMap.PlaceSearch", "AMap.Event"],
     key: "e661b00bdf2c44cccf71ef6070ef41b8",
     version: "2.0",
   });
-
   const map = new AMap.Map(mapEl.value, {
     WebGLParams: {
       preserveDrawingBuffer: true,
     },
     resizeEnable: true,
+    center: [center.value.lat, center.value.lng], //中心坐标
+    zoom: 10, //缩放级别
   });
   const placeSearch = new AMap.PlaceSearch({
     pageSize: 5,
@@ -77,8 +108,11 @@ watchEffect(async (onCleanup) => {
     autoFitView: true,
   });
   const setSearch = (data) => {
-    console.log('setSearch', data);
-    let city = data.cityname == data.adname? data.cityname : (data.cityname + data.adname);
+    console.log("setSearch", data);
+    let city =
+      data.cityname == data.adname
+        ? data.cityname
+        : data.cityname + data.adname;
     searchInfo.value = {
       text: data.pname + city + data.address,
       lat: data.location.lat,
@@ -92,12 +126,21 @@ watchEffect(async (onCleanup) => {
     showSearch.value = false;
   });
   let clickMarker;
-
-  map.on("click", function (e) {
+  //绑定地图移动与缩放事件
+  map.on("moveend", () => {
+    info.value = getMapInfo();
+  });
+  map.on("zoomend", () => {
+    info.value = getMapInfo();
+  });
+  map.on("click", async function (e) {
     // 获取点击位置的经纬度坐标
+    const KEY = "e661b00bdf2c44cccf71ef6070ef41b8";
     var latitude = e.lnglat.lat;
     var longitude = e.lnglat.lng;
-
+    let location = [longitude, latitude];
+    const res = await regeocoder(location);
+    console.log(e, res, "function");
     searchInfo.value = {
       text: "",
       lat: latitude,
@@ -126,42 +169,79 @@ watchEffect(async (onCleanup) => {
       }
     }, 500);
   });
-
+  async function regeocoder(lnglatXY) {
+    return AMap.plugin("AMap.Geocoder", function () {
+      var geocoder = new AMap.Geocoder({
+        radius: 1000,
+        extensions: "all",
+      });
+      return geocoder.getAddress(lnglatXY, function (status, result) {
+        if (status === "complete" && result.info === "OK") {
+          console.log(
+            result.regeocode.formattedAddress,
+            "result.regeocode.formattedAddress"
+          );
+          searchInfo.value.text = result.regeocode.formattedAddress;
+          return result.regeocode.formattedAddress;
+        }
+      });
+      // if(!marker){
+      //     marker = new AMap.Marker();
+      //     map.add(marker);
+      // }
+      // marker.setPosition(lnglatXY);
+    });
+  }
   const getMapInfo = (): MapInfo => {
     var zoom = map.getZoom(); //获取当前地图级别
-    var center = map.getCenter();
+    var centers = map.getCenter();
     return {
       text: "",
       zoom,
-      lat: center.lat,
-      lng: center.lng,
+      lat: centers.lat,
+      lng: centers.lng,
     };
   };
-  //绑定地图移动与缩放事件
-  map.on("moveend", () => {
-    info.value = getMapInfo();
-  });
-  map.on("zoomend", () => {
-    info.value = getMapInfo();
-  });
   searchAMap.value = placeSearch;
+};
+const renderGgMapGd = async () => {
+  // const map = new google.maps.Map(mapEl.value, {
+  //   zoom: 10,
+  //   center: center.value,
+  //   mapTypeId: "roadmap",
+  // });
+};
+onMounted(async () => {
+  caseInfoData.value = await getCaseInfo(caseId.value);
+  if (caseInfoData.value?.latAndLong) {
+    let centerObj = caseInfoData.value.latAndLong.split(",").reverse()
+    center.value.lat = parseFloat(centerObj[0]);
+    center.value.lng = parseFloat(centerObj[1]);
+  }
+  renderMapGd()
+  renderGgMapGd()
+});
+watchEffect(async (onCleanup) => {
+  if (!mapEl.value || !resultEl.value) {
+    return;
+  }
 
-  onCleanup(() => {
-    searchAMap.value = null;
-    map.destroy();
-  });
+  // onCleanup(() => {
+  //   searchAMap.value = null;
+  //   map.destroy();
+  // });
 });
-var dataURLtoBlob =  function (dataurl){
-        var arr = dataurl.split(','),
-            mime = arr[0].match(/:(.*?);/)[1],
-            bstr = atob(arr[1]),
-            n = bstr.length,
-            u8arr = new Uint8Array(n);
-       while (n--) {
-           u8arr[n] = bstr.charCodeAt(n);
-       }
-       return new Blob([u8arr], { type: mime })
+var dataURLtoBlob = function (dataurl) {
+  var arr = dataurl.split(","),
+    mime = arr[0].match(/:(.*?);/)[1],
+    bstr = atob(arr[1]),
+    n = bstr.length,
+    u8arr = new Uint8Array(n);
+  while (n--) {
+    u8arr[n] = bstr.charCodeAt(n);
   }
+  return new Blob([u8arr], { type: mime });
+};
 const search = debounce((keyword: string) => {
   searchAMap.value.search(keyword);
 }, 1000);
@@ -172,18 +252,19 @@ watchEffect(() => {
 defineExpose<QuiskExpose>({
   submit() {
     return new Promise<MapImage>((resolve) => {
-        console.log('searchInfo', searchInfo.value, mapEl.value);
-        if (mapEl.value) {
+      console.log("searchInfo", searchInfo.value, mapEl.value);
+      if (mapEl.value) {
         const canvas = mapEl.value.querySelector("canvas") as HTMLCanvasElement;
-        console.log(canvas, 'canvas');
-        canvas && canvas.toBlob((blob) => resolve({ blob, search: searchInfo.value! }))// || resolve({ search: searchInfo.value! });
-        if(!canvas){
+        console.log(canvas, "canvas");
+        canvas &&
+          canvas.toBlob((blob) => resolve({ blob, search: searchInfo.value! })); // || resolve({ search: searchInfo.value! });
+        if (!canvas) {
           //div内容生成图片
           html2canvas(mapEl.value, {
             useCORS: true, // 添加这个选项以解决跨域问题
           }).then((canvas) => {
             let imgUrl = canvas.toDataURL("image/png");
-            let blob = dataURLtoBlob(imgUrl)
+            let blob = dataURLtoBlob(imgUrl);
             resolve({ blob, search: searchInfo.value! });
           });
         }

+ 2 - 0
src/view/case/draw/slider.vue

@@ -144,6 +144,7 @@ const imageLabel = reactive(
 
 watchEffect(async () => {
   if (imageLabel.file) {
+    console.log(imageLabel.file,'watchEffect', customImage);
     emit("update:addShape", customImage, imageLabel.file);
     imageLabel.removeFile();
     // ElMessage.info("请前往右边画板选择为止单击添加图例");
@@ -156,6 +157,7 @@ watchEffect(async () => {
   padding: 10px 24px;
   overflow-y: auto;
   height: 100%;
+  color: #000;
   h3 {
     font-size: 16px;
     font-weight: normal;

+ 31 - 10
src/view/case/help.ts

@@ -5,12 +5,13 @@ import {
   SceneTypePaths,
 } from "@/constant/scene";
 import { alert } from "@/helper/message";
-import { getCaseSceneList, getSyncSceneInfo } from "@/store/case";
+import { getCaseSceneList, getSyncSceneInfo, getUrlSrc } from "@/store/case";
 import { CaseTagging } from "@/store/caseTagging";
 import { ModelScene, QuoteScene, Scene, SceneType } from "@/store/scene";
 import { transformSWToken, user } from "@/store/user";
 import { base64ToBlob, urlToBlob, drawImage } from "@/util";
 import { ref, watchEffect } from "vue";
+import { imageCropper } from "@/view/system/quisk";
 
 export type MenuItem = {
   key: string;
@@ -101,9 +102,10 @@ export const getQuery = (
   share: boolean = false,
   single: boolean = false
 ) =>
-  `${getFuseCodeLink(caseId, true)}${share ? "&share=1" : ""}${
-    single ? "&single=1" : ""
-  }#show/summary`;
+  getUrlSrc({type: 99}, caseId)
+  // `${getFuseCodeLink(caseId, true)}${share ? "&share=1" : ""}${
+  //   single ? "&single=1" : ""
+  // }#show/summary`;
 
 // 查看
 export const gotoQuery = (caseId: number) => {
@@ -186,7 +188,7 @@ export const getFuseImage = async (
 ) => {
   const typeMap = await getIframeSceneType(iframe);
   let blob: Blob | null = null;
-
+  let newUrl: string | null = null;
   switch (typeMap?.type) {
     case FuseImageType.FUSE:
       console.error(width, height);
@@ -196,11 +198,29 @@ export const getFuseImage = async (
       break;
     case FuseImageType.KANKAN:
       console.error("截图尺寸", width, height);
-      if(typeMap.sdk.store?.getValue('metadata')?.floorPlanUser) {
-        let num = typeMap.sdk.store?.getValue('metadata').num
-       // /oss/scene_view_data/YZL-jm-3EOpLfZxim9/user/cad-style-3.jpg
-       blob = await urlToBlob(`/oss/scene_view_data/${num}/user/cad-style-3.jpg`)
-       console.error("截图尺寸2", blob);
+      if(typeMap.sdk.Camera.mode == 'floorplan') {//判断户型图模式是否为平面图模式,如果是则获取平面图的截图进行裁剪处理
+        if(typeMap.sdk.store?.getValue('metadata')?.floorPlanUser) {
+          let num = typeMap.sdk.store?.getValue('metadata').num
+          let floorId = typeMap.sdk.core.get('Player').model.currentFloor.floorIndex
+          let img = ''
+          if (typeMap.sdk.store.getValue('flooruser').type === 'image') {
+              img = `floor-upload-${floorId}.jpg`
+          } else {
+              img = `cad-style-3-${floorId}.jpg`
+          }
+          newUrl = `${getUrlSrc({type: 102})}/profile/scene_view_data/${num}/user/${img}?t=${Date.now()}`
+         // /oss/scene_view_data/YZL-jm-3EOpLfZxim9/user/cad-style-3.jpg
+         let newBlob = await urlToBlob(newUrl)
+         if(newBlob.type != 'text/html'){
+          blob = await imageCropper({
+            img: newBlob,
+            fixed: [width, height]
+           })
+         }
+         console.error("截图尺寸2", blob);
+        } else {
+          alert("暂未获取到平面图,请前往三维场景制作并保存");
+        }
       } else {
         const result = await typeMap.sdk.Camera.screenshot(
           [{ width: width, height: height, name: "2k", bgOpacity: 0 }],
@@ -210,6 +230,7 @@ export const getFuseImage = async (
       }
      
       break;
+      break;
     case FuseImageType.LASER:
       blob = await new Promise<Blob | null>((resolve) => {
         typeMap.sdk.scene

+ 3 - 3
src/view/case/photos/draggable.vue

@@ -24,7 +24,7 @@
         :key="item.id"
         @click="handleItem(item.id)"
       >
-        <img class="itemImg" :src="item.imgUrl" alt="" />
+        <img class="itemImg" :src="getUrlSrc({type: 102}) +'/'+ item.imgUrl" alt="" />
         <div class="text">
           <div :title="item.imgInfo">{{ item.imgInfo }}</div>
           <EditPen @click.stop="handleEdit(item)" class="EditPen" />
@@ -41,7 +41,7 @@
 
 <script setup lang="ts">
 import { ref, onMounted, watch, computed } from "vue";
-import { caseImgList, CaseImg, caseDel, caseUpdateSort } from "@/store/case";
+import { caseImgList, CaseImg, caseDel, caseUpdateSort, getUrlSrc } from "@/store/case";
 import { VueDraggable } from "vue-draggable-plus";
 import { openErrorMsg } from "@/request/errorMsg.js";
 import { addCaseImgFile } from "../quisk";
@@ -83,7 +83,7 @@ async function onChange({ newIndex, oldIndex }) {
     let apiList = searchList.value.map((item, index) => {
       return { ...item, sort: index + 1 };
     });
-    console.log(apiList);
+    console.log(apiList,'apiList');
     await caseUpdateSort(apiList);
     emit("changeList", apiList);
   }, 500);

+ 18 - 12
src/view/case/photos/index.vue

@@ -51,22 +51,19 @@
           <i class="iconfont icon-text1" />
           文本</el-button
         >
-        <el-button @click="handleSave" class="save">保存</el-button>
+        <el-button @click="handleSave" >保存</el-button>
         <el-button
           @click="handleExport"
-          class="opt"
           v-if="newlist.length"
           :loading="!isSenseLoaded"
           :disabled="!isSenseLoaded"
           >导出</el-button
         >
-        <el-button @click="backPageHandler" class="opt">返回</el-button>
+        <el-button @click="backPageHandler">返回</el-button>
 
-        <el-button @click="handleClear" v-if="hasDrawData" class="opt"
-          >清空</el-button
+        <el-button @click="handleClear" v-if="hasDrawData">清空</el-button
         >
-        <el-button @click="handleFree" v-if="isShowExitEdit" class="opt"
-          >退出编辑</el-button
+        <el-button @click="handleFree" v-if="isShowExitEdit" >退出编辑</el-button
         >
       </div>
 
@@ -86,7 +83,8 @@
 import { onMounted, ref, computed, onUnmounted, reactive } from "vue";
 import { Menu, FullScreen } from "@element-plus/icons-vue";
 import { Swiper, SwiperSlide } from "swiper/vue";
-import { router } from "@/router";
+import { router, RouteName } from "@/router";
+import { getUrlSrc } from "@/store/case";
 import "swiper/css";
 // import { addCaseFile } from "@/store/caseFile";
 import { addCaseImgFile, addCaseImgFileAll } from "../quisk";
@@ -153,8 +151,13 @@ function refresh() {
     childRef.value.getList();
   }
 }
-const changeList = async (list) => {
-  console.log("changeList", list, loadedDrawData.value);
+const changeList = async (lists) => {
+  const list = lists.map(item => {
+    return {
+      ...item,
+      imgUrl: getUrlSrc({type: 102}) +'/'+ item.imgUrl,
+    };
+  });
   //同步数据
   if (!loadedDrawData.value) {
     const res = await getCaseImgTagData(caseId.value);
@@ -183,6 +186,8 @@ const changeList = async (list) => {
     }
   });
   newlist.value = newList;
+  console.log("changeListnewList", newlist.value);
+
   const arr = [];
   newList.map((i) => arr.push(JSON.parse(JSON.stringify(i))));
 
@@ -269,7 +274,7 @@ const renderCanvas = () => {
           if (data && data.imgUrl) {
             if (save) {
               // debugger;
-              saveAs(data.imgUrl, title);
+              saveAs(getUrlSrc({type: 102}) +'/'+  data.imgUrl, title);
             }
           }
           window.scene.endScreenshot();
@@ -343,7 +348,8 @@ const handleMark = () => {
 };
 
 const backPageHandler = () => {
-  router.back();
+  // router.back();
+  router.replace({ name: RouteName.material, params: { caseId: caseId.value } });
 };
 
 const handleSymbol = () => {

+ 1 - 1
src/view/case/quisk.ts

@@ -8,7 +8,7 @@ import EditEshapeTable, {
 } from "./draw/editEshapeTable.vue";
 import SceneList from "./sceneList.vue";
 import SelectFuseImage, { FuseImage } from "./draw/selectFuseImage.vue";
-import SelectMapImage, { MapImage } from "./draw/selectMapImage.vue";
+import SelectMapImage, { MapImage } from "./draw/selectMapImages.vue";
 import { quiskMountFactory } from "@/helper/mount";
 import { nextTick } from "vue";
 import { ui18n } from '@/i18n'

+ 5 - 2
src/view/case/records/index.vue

@@ -480,6 +480,7 @@ import {
   saveCaseInquestInfo,
   exportCaseInquestInfo,
   getCaseInquestInfoOld,
+  getUrlSrc
 } from "@/store/case";
 import { ElMessage, ElMessageBox } from "element-plus";
 import saveAs from "@/util/file-serve";
@@ -633,7 +634,8 @@ const addWitnessInfo = async () => {
   }
 };
 const backPageHandler = () => {
-  router.back();
+  // router.back();
+  router.replace({ name: RouteName.material, params: { caseId: caseId.value } });
 };
 const removeWitnessInfo = async (index) => {
   if (await confirm("确定要删除此数据?")) {
@@ -673,8 +675,9 @@ const handleExport = async () => {
   let params = { ...data, caseId: caseId.value, inquestFileId };
   await saveCaseInquestInfo(params);
   const res = await exportCaseInquestInfo(fileId.value);
+  const href = URL.createObjectURL(res)
   console.log("res", res);
-  saveAs(res, `勘验笔录.docx`);
+  saveAs(href, `勘验笔录.docx`);
 };
 </script>
 

+ 2 - 1
src/view/case/records/manifest.vue

@@ -173,6 +173,7 @@ import {
   getCaseDetailInfo,
   saveCaseDetailInfo,
   exportCaseDetailInfo,
+  getUrlSrc,
 } from "@/store/case";
 import saveAs from "@/util/file-serve";
 import { ElMessage } from "element-plus";
@@ -296,7 +297,7 @@ const handleExport = async () => {
   await saveCaseDetailInfo(props.caseId, data);
   const res = await exportCaseDetailInfo(props.caseId);
   console.log("res", res);
-  saveAs(res, `${props.title}_提取清单.docx`);
+  saveAs(getUrlSrc({type: 102}) +'/'+ res, `${props.title}_提取清单.docx`);
 };
 const initInfo = async () => {
   const res = await getCaseDetailInfo(props.caseId);

+ 2 - 2
src/view/dossier/index.vue

@@ -17,7 +17,7 @@
         :before-remove="removeFile"
       >
         <div type="primary" :disabled="!!file">
-          <div>点击或拖拽文件上传</div>
+          <div>点击文件上传</div>
           <div class="">
             支持≤10MB jpg\png\jpeg格式图片,及≤500MB pdf\doc文件上传
           </div>
@@ -39,7 +39,7 @@
         :before-remove="removeFile"
       >
         <div type="primary" :disabled="!!file">
-          <div>点击或拖拽文件上传</div>
+          <div>点击文件上传</div>
           <div class="">
             支持≤10MB jpg\png\jpeg格式图片,及≤500MB pdf\doc文件上传
           </div>

+ 1 - 1
src/view/layout/index.vue

@@ -165,7 +165,7 @@ const handleClick = (command: string) => {
     .view {
       flex: 0 0 auto;
       width: calc(100% - 16.66rem);
-      background-color: var(--bgColor);
+      background-color: #000;
       // flex-direction: column;
       display: flex;
 

+ 73 - 30
src/view/layout/top/index.vue

@@ -1,12 +1,29 @@
 <template>
   <div class="header-top">
-    <div class="title" >
+    <div class="title">
       <div class="span" :title="title">{{ title }}</div>
-      <div class="edit" style="white-space: nowrap;text-overflow: ellipsis;">{{$t('program.sceneDown.edit')}}</div>
+      <div class="edit" style="white-space: nowrap; text-overflow: ellipsis">
+        {{ $t("program.sceneDown.edit") }}
+      </div>
     </div>
     <div class="oper-btns">
-      <el-button style="padding: 5px 10px" type="primary" @click="handlemtk">媒体库</el-button>
-      <el-button style="padding: 5px 10px" type="primary" @click="handleSee">预览案件</el-button>
+      <el-input
+        v-model="bindExample.caseAddress"
+        placeholder="输入名称搜索"
+        clearable
+        maxlength="100"
+        disabled
+      >
+        <template #append>
+          <el-button :icon="Search" @click="searchAMapAddress" />
+        </template>
+      </el-input>
+      <el-button style="padding: 5px 10px;margin-left: 12px;" type="primary" @click="handlemtk"
+        >媒体库</el-button
+      >
+      <el-button style="padding: 5px 10px" type="primary" @click="handleSee"
+        >预览案件</el-button
+      >
     </div>
     <!-- <div class="oper-btns" v-if="user.info">
       <div class="user-menu">
@@ -38,10 +55,27 @@ import { refreshRole, roleId } from "@/store/role";
 import { RouteName, router } from "@/router";
 import { confirm } from "@/helper/message";
 import { updatePwd } from "@/view/system/quisk";
-import { computed, ref, watch } from "vue";
+import { computed, ref, watch, onMounted } from "vue";
 import { addCaseScenes } from "@/view/originalPhoto/quisk";
 import { windowOpen } from "@/util";
-import { ui18n } from '@/i18n'
+import { ui18n } from "@/i18n";
+import { Search } from "@element-plus/icons-vue";
+import { selectMapImage } from "@/view/case/quisk";
+import { setExample } from "@/app/criminal/store/example";
+
+const bindExample = ref({
+  caseTitle: "",
+  caseNum: "",
+  caseCategory: "",
+  crimeTime: "",
+  caseRegion: [],
+  caseAddress: "",
+  homicideCase: 0,
+  criminalCase: 0,
+  latAndLong: "",
+  latAndLongs: "",
+  criminalType: "",
+});
 
 refreshRole();
 const caseId = computed(() => {
@@ -50,31 +84,40 @@ const caseId = computed(() => {
     return Number(caseId);
   }
 });
-getCaseInfo(caseId.value)
-// const useCaseStore = getCaseInfoData();
-const title =  computed(() => getCaseInfoData().caseTitle);
-console.log("useCaseStore", title);
-// const title = ref('')
-// const getInfo = async () => {
-//   const res = await getCaseInfo(caseId.value);
-//   console.log('getInfo', res, getCaseInfoData())
-//   title.value = getCaseInfoData() || res?.caseTitle || "";
-// };
-// getInfo();
-watch(() => title.value, () => {
-  document.title = title.value;
-})
+onMounted(async () => {
+  const caseInfo = await getCaseInfo(caseId.value);
+  if (caseInfo) {
+    bindExample.value = JSON.parse(JSON.stringify(caseInfo));
+  }
+});
+const title = computed(() => getCaseInfoData()?.caseTitle);
+watch(
+  () => title.value,
+  () => {
+    document.title = title.value;
+  }
+);
 const loginoutRaw = async () => {
   await logoutRaw();
   roleId.value = "";
   router.replace({ name: RouteName.login });
 };
 const handlemtk = () => {
-  windowOpen({type: 'tab', data: 'media'});
+  windowOpen({ type: "tab", data: "media" });
+};
+const searchAMapAddress = async () => {
+  const data = await selectMapImage({ text: true });
+  console.log("searchAMapAddress", data);
+  if (!data?.search) return;
+  bindExample.value.caseAddress = data.search.text;
+  bindExample.value.latAndLong = `${data.search.lat},${data.search.lng}`;
+  bindExample.value.latAndLongs = `${data.search.lng},${data.search.lat}`;
+  await setExample({...bindExample.value, caseId :caseId.value })
 };
 const handleSee = () => {
-  let url = getUrlSrc({type: 99}, caseId.value)
-  windowOpen({url,title: ui18n.t('sceneHome.caseView')});
+  let url = getUrlSrc({ type: 99 }, caseId.value);
+  console.log("handleSee", url, title.value);
+  windowOpen({ url, title: title.value});
 };
 const logout = async () => {
   if (await confirm("确定要退出登录吗?")) {
@@ -91,17 +134,17 @@ const updatePwdHandler = async () => {
 
 <style lang="scss" scoped>
 @import "./style.scss";
-.header-top{
+.header-top {
   width: 100%;
 }
-.title{
+.title {
   // display: inline-block;
   max-width: calc(50% + 300px);
-  .span{
-  overflow:hidden;
-  white-space: nowrap;
-  text-overflow: ellipsis;
-  -o-text-overflow:ellipsis;
+  .span {
+    overflow: hidden;
+    white-space: nowrap;
+    text-overflow: ellipsis;
+    -o-text-overflow: ellipsis;
   }
 }
 </style>

+ 2 - 0
src/view/layout/top/style.scss

@@ -40,6 +40,8 @@
   }
 
   .oper-btns {
+    display    : flex;
+    align-items: center;
     .user-menu {
       display    : flex;
       align-items: center;

+ 21 - 12
src/view/material/index.vue

@@ -4,17 +4,17 @@
       <el-tab-pane label="现场图" name="1">
         <div class="xct">
           <el-button
-            style="width: calc(50% - 7px)"
+            class="w-full mt-4"
             @click="gotoDraw(BoardType.scene, -1)"
           >
             绘制平面图
           </el-button>
-          <el-button
+          <!-- <el-button
             style="width: calc(50% - 6px)"
             @click="handleAiItem"
           >
             AI 平面图
-          </el-button>
+          </el-button> -->
           <el-button
             class="w-full mt-4"
             style="margin-left: 0"
@@ -66,7 +66,7 @@
             :before-remove="removeFile"
           >
             <div type="primary" :disabled="!!file">
-              <div>点击或拖拽文件上传</div>
+              <div>点击文件上传</div>
               <div class="">支持 pdf、word 上传</div>
             </div>
           </el-upload>
@@ -124,6 +124,8 @@ import { updateByTreeFileLists, getByTreeFileLists } from "@/store/case";
 import { Delete, Edit } from "@element-plus/icons-vue";
 import { setTypeFile } from "../originalPhoto/quisk";
 import viewImg from "@/components/viewImg/index.vue"
+import { getUrlSrc } from "@/store/case";
+import saveAs from "@/util/file-serve";
 import { delCaseFile, } from "@/store/caseFile";
 const caseId = computed(() => (router.currentRoute.value?.params?.caseId));
 const active = ref(true);
@@ -253,15 +255,22 @@ function getExtension (name) {
 }
 // 下载文件
 function downloadFile(sourceUrl, fileName,) {
-      const link = document.createElement('a');
-  link.style.display = 'none';
-  // 设置下载地址
-  link.setAttribute('href', sourceUrl);
+  console.log("downloadFile", sourceUrl, fileName + getExtension(sourceUrl));
+  // const link = document.createElement('a');
+  // // link.style.display = 'none';
+  // // 设置下载地址
+  // link.href = getUrlSrc({type: 102}) + '/' + sourceUrl;
+  saveAs(getUrlSrc({type: 102}) +'/'+  sourceUrl, fileName);
+  // link.download = fileName + getExtension(sourceUrl);
+  // link.setAttribute('download', fileName + getExtension(sourceUrl));
+  // link.click();
+  // link.remove();
+  // link.setAttribute('href', sourceUrl);
   // 设置文件名
-  link.setAttribute('download', fileName + getExtension(sourceUrl));
-  document.body.appendChild(link);
-  link.click();
-  document.body.removeChild(link);
+  // link.setAttribute('download', fileName + getExtension(sourceUrl));
+  // document.body.appendChild(link);
+  // link.click();
+  // document.body.removeChild(link);
 }
 
 function handleClose(e) {

+ 307 - 169
src/view/mediaLibrary/TableComponent.vue

@@ -5,10 +5,9 @@
       <el-form-item class="formitem">
         <el-input
           v-model="searchForm.name"
-          @change="submitClick"
+          @input="submitClick"
           @keydown.enter="submitClick"
           :placeholder="$t('mediaLibrary.addFilePlace')"
-
           size="default"
           :prefix-icon="Search"
         ></el-input>
@@ -36,43 +35,57 @@
         </el-select>
       </el-form-item>
       <el-form-item style="margin-right: 0px">
-        <el-select class="groupingSelcet"
+        <el-select
+          class="groupingSelcet"
           v-model="searchForm.dictId"
           style="width: 120px"
           @change="submitClick"
           :placeholder="$t('mediaLibrary.fileType.0')"
         >
           <el-option
-            v-for="(item, index) in [...allList,...dictIdList]"
+            v-for="(item, index) in [...allList, ...dictIdList]"
             :key="index"
             :label="item.dictName"
             :value="item.id"
           />
         </el-select>
         <div class="grouping" @click="handleAddfz">
-          <img :src="groupingSvg" alt="">
+          <img :src="groupingSvg" alt="" />
         </div>
       </el-form-item>
       <el-form-item style="float: right; margin-right: 0">
-        <el-button   @click="windowOpen({type: 'photography', library: true})">{{$t('mediaLibrary.photography')}}</el-button>
-        <el-button v-if="modeling == 1"  @click="windowOpen({type: 'modeling', library: true})">{{$t('mediaLibrary.Modeling')}}</el-button>
+        <el-button
+          @click="windowOpen({ type: 'photography', library: true })"
+          >{{ $t("mediaLibrary.photography") }}</el-button
+        >
+        <el-button
+          v-if="modeling == 1"
+          @click="windowOpen({ type: 'modeling', library: true })"
+          >{{ $t("mediaLibrary.Modeling") }}</el-button
+        >
         <!--v-if="photography == 1" <el-button type="primary" @click="handleAddfz">{{$t('mediaLibrary.grouping')}}</el-button> -->
-        <el-button type="primary" @click="handleAdd">{{$t('mediaLibrary.upload')}}</el-button>
+        <el-button type="primary" @click="handleAdd">{{
+          $t("mediaLibrary.upload")
+        }}</el-button>
       </el-form-item>
     </el-form>
     <!-- 表格 -->
     <el-table
-    tooltip-effect="dark"
-        ref="tableRef"
-        size="large"
+      tooltip-effect="dark"
+      ref="tableRef"
+      size="large"
       :data="tableData"
       style="width: 100%"
       :row-class-name="tableRowClassName"
     >
-      <el-table-column prop="name" :label="$t('program.case.title')" min-width="300px">
+      <el-table-column
+        prop="name"
+        :label="$t('program.case.title')"
+        min-width="300px"
+      >
         <template #default="scope">
           <a
-            style="color: var(--primaryColor);cursor: pointer;"
+            style="color: var(--primaryColor); cursor: pointer"
             v-if="scope.row.fileUrl"
             target="_blank"
             :title="scope.row.name"
@@ -91,20 +104,28 @@
         :min-width="column.width"
         :label="column.label"
       ></el-table-column>
-      <el-table-column align="center" :label="$t('mediaLibrary.operate')" width="150">
+      <el-table-column
+        align="center"
+        :label="$t('mediaLibrary.operate')"
+        width="150"
+      >
         <template #default="{ row }">
           <!-- <el-button type="primary" text style="color: var(--primaryColor)" size="small" @click="handleEdit(row)">
             编辑
           </el-button> -->
-          <span class="oper-span" style="color: var(--primaryColor)" @click="handleEdit(row)">
-            {{$t('sys.edit')}}
+          <span
+            class="oper-span"
+            style="color: var(--primaryColor)"
+            @click="handleEdit(row)"
+          >
+            {{ $t("sys.edit") }}
           </span>
           <span
             class="oper-span"
             @click="del(row)"
             style="color: var(--primaryColor)"
           >
-          {{$t('sys.delete')}}
+            {{ $t("sys.delete") }}
           </span>
         </template>
       </el-table-column>
@@ -112,7 +133,7 @@
     <!-- 分页 -->
     <el-pagination
       class="mt-3"
-      background 
+      background
       @size-change="handleSizeChange"
       @current-change="handleCurrentChange"
       :current-page="currentPage"
@@ -147,15 +168,23 @@
             :before-remove="removeFile"
           >
             <el-button type="primary">
-              {{$t('sys.upload')}}
+              {{ $t("sys.upload") }}
             </el-button>
           </el-upload>
         </el-form-item>
         <el-form-item
-          :label="dialogData.title == $t('sys.upload') ? $t('mediaLibrary.dictName') : $t('mediaLibrary.setGrouping')"
+          :label="
+            dialogData.title == $t('sys.upload')
+              ? $t('mediaLibrary.dictName')
+              : $t('mediaLibrary.setGrouping')
+          "
           :label-width="formLabelWidth"
         >
-          <el-select style="width: 100%" v-model="addForm.dictId" :placeholder="$t('mediaLibrary.tips.dictId')">
+          <el-select
+            style="width: 100%"
+            v-model="addForm.dictId"
+            :placeholder="$t('mediaLibrary.tips.dictId')"
+          >
             <el-option
               v-for="(item, index) in dictIdList"
               :key="index"
@@ -166,18 +195,19 @@
         </el-form-item>
         <div v-if="dialogData.title == $t('sys.upload')">
           <div style="margin-bottom: 10px">
-            {{$t('mediaLibrary.tips.uplooadfiletype')}}{{$t('mediaLibrary.tips.uplooadSize')}}
+            {{ $t("mediaLibrary.tips.uplooadfiletype")
+            }}{{ $t("mediaLibrary.tips.uplooadSize") }}
           </div>
           <!-- <span>注意:模型需使用zip包上传。包含贴图、模型、mtl文件,包内不得包含文件夹。</span> -->
           <div style="margin-bottom: 10px">
             <div>
-              {{$t('mediaLibrary.tips.objtips')}}
+              {{ $t("mediaLibrary.tips.objtips") }}
             </div>
             <img style="width: 150px" :src="obj" alt="" />
           </div>
           <div style="margin-bottom: 10px">
             <div>
-              {{$t('mediaLibrary.tips.osgbtips')}}
+              {{ $t("mediaLibrary.tips.osgbtips") }}
             </div>
             <img style="width: 150px" :src="osgb" alt="" />
           </div>
@@ -185,70 +215,136 @@
       </el-form>
       <template #footer>
         <div class="dialog-footer">
-          <el-button @click="dialogData.show = false">{{$t('sys.cancel')}}</el-button>
+          <el-button @click="dialogData.show = false">{{
+            $t("sys.cancel")
+          }}</el-button>
           <el-button type="primary" @click="handleuploadAdd">
-            {{$t('sys.enter')}}
+            {{ $t("sys.enter") }}
           </el-button>
         </div>
       </template>
     </el-dialog>
     <!-- 修改分组 -->
-    <el-dialog v-model="dialogData.fzshow" :title="$t('mediaLibrary.grouping')" width="400">
-    <el-form  label-position="top" :model="form" label-width="50">
-      <el-form-item :label="$t('mediaLibrary.addgrouping')" :label-width="formLabelWidth">
-        <el-input v-model="dialogData.fzName" maxLength="100" style="width: 278px;" />
-        <el-button style="margin-left: 20px;width: 60px" @click="handlefzAdd">{{$t('common.add')}}</el-button>
-      </el-form-item>
-      <div class="itemTitle">
-        <p>{{$t('mediaLibrary.groupingList')}}</p>
-        <div class="itemTitleList">
-          <div class="itemTitle-list" v-for="(item, index) in dictIdList" :key="index">
-            <span class="name">{{ item.dictName }}</span>
-            <span class="del" @click="hanleFzDle(item)">{{$t('sys.delete')}}</span>
+    <el-dialog
+      v-model="dialogData.fzshow"
+      :title="$t('mediaLibrary.grouping')"
+      width="400"
+    >
+      <el-form label-position="top" :model="form" label-width="50">
+        <el-form-item
+          :label="$t('mediaLibrary.addgrouping')"
+          :label-width="formLabelWidth"
+        >
+          <el-input
+            v-model="dialogData.fzName"
+            maxLength="100"
+            style="width: 278px"
+          />
+          <el-button
+            style="margin-left: 20px; width: 60px"
+            @click="handlefzAdd"
+            >{{ $t("common.add") }}</el-button
+          >
+        </el-form-item>
+        <div class="itemTitle">
+          <p>{{ $t("mediaLibrary.groupingList") }}</p>
+          <div class="itemTitleList">
+            <div class="itemTitle-list" v-if="!dictIdList?.length">
+              {{ $t("program.undata") }}
+            </div>
+            <div
+              class="itemTitle-list"
+              v-for="(item, index) in dictIdList"
+              :key="index"
+            >
+              <span class="name">{{ item.dictName }}</span>
+              <span class="del" @click="hanleFzDle(item)">{{
+                $t("sys.delete")
+              }}</span>
+            </div>
           </div>
         </div>
-      </div>
-    </el-form>
-    <template #footer>
-      <div class="dialog-footer">
-          <el-button @click="dialogData.fzshow = false">{{$t('sys.cancel')}}</el-button>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="dialogData.fzshow = false">{{
+            $t("sys.cancel")
+          }}</el-button>
           <el-button type="primary" @click="dialogData.fzshow = false">
-            {{$t('sys.enter')}}
+            {{ $t("sys.enter") }}
           </el-button>
+        </div>
+      </template>
+    </el-dialog>
+    <!-- //查看资源下载 -->
+    <el-dialog
+      v-model="dialogDowm.show"
+      :title="$t("sys.query")"
+      :width="dialogDowm.type == 4 ? 800 : 600"
+      @close="handleClose"
+    >
+      <div class="showContent">
+        <img
+          style="width: 100%; object-fit: cover"
+          :src="dialogDowm.url"
+          alt=""
+          v-if="dialogDowm.type == 0"
+        />
+        <video
+          ref="audio"
+          controls
+          style="width: 100%; object-fit: cover; max-height: 300px"
+          :style="{ height: dialogDowm.type == 2 ? '50px' : 'auto' }"
+          :src="dialogDowm.url"
+          alt=""
+          v-else-if="dialogDowm.type == 1 || dialogDowm.type == 2"
+        />
+        <iframe
+          style="width: 100%; height: 400px"
+          v-else
+          :src="dialogDowm.url"
+          frameborder="0"
+        ></iframe>
       </div>
-    </template>
-  </el-dialog>
-  <!-- //查看资源下载 -->
-  <el-dialog v-model="dialogDowm.show" title="资源查看" :width="dialogDowm.type == 4?800:600" @close="handleClose">
-    <div class="showContent">
-      <img style="width: 100%;object-fit: cover;" :src="dialogDowm.url" alt="" v-if="dialogDowm.type == 0"/>
-      <video ref="audio" controls style="width: 100%;object-fit: cover;" :style="{height:dialogDowm.type == 2?'50px':'auto'}":src="dialogDowm.url" alt="" v-else-if="dialogDowm.type == 1 || dialogDowm.type == 2"/>
-      <iframe style="width: 100%; height: 400px" v-else :src="dialogDowm.url" frameborder="0"></iframe>
-  </div>
-    <template #footer>
-      <div class="dialog-footer">
-          <el-button @click="dialogDowm.show = false">{{$t('sys.cancel')}}</el-button>
-          <el-button v-if="dialogDowm.type == 0" type="primary" @click="hanleDown">
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="dialogDowm.show = false">{{
+            $t("sys.cancel")
+          }}</el-button>
+          <el-button
+            v-if="dialogDowm.type == 0"
+            type="primary"
+            @click="hanleDown"
+          >
             下载
           </el-button>
-      </div>
-    </template>
-  </el-dialog>
+        </div>
+      </template>
+    </el-dialog>
   </div>
 </template>
 
 <script setup>
 import { Search } from "@element-plus/icons-vue";
 import { ref, watch, onMounted } from "vue";
-import { getByKeyList, getfzdel, getdictFiledel, getaddOrUpdate, setFileaddOrUpdate, uploadFile, getUrlSrc } from "@/store/case";
+import {
+  getByKeyList,
+  getfzdel,
+  getdictFiledel,
+  getaddOrUpdate,
+  setFileaddOrUpdate,
+  uploadFile,
+  getUrlSrc,
+} from "@/store/case";
 import dayjs from "dayjs";
 import obj from "@/assets/images/obj.jpg";
 import osgb from "@/assets/images/osgb.jpg";
 import { ElMessage, ElMessageBox, genFileId } from "element-plus";
-import { ui18n } from '@/i18n'
+import { ui18n } from "@/i18n";
 import { windowOpen } from "@/util";
 import { getUrlData } from "@/store/user";
 import groupingSvg from "@/assets/images/grouping.svg";
+import { ElLoading } from "element-plus";
 
 // 定义 props
 const props = defineProps({
@@ -278,46 +374,51 @@ const addForm = ref({
 });
 const dialogDowm = ref({
   show: false,
-  url: '',
-  type: '',
+  url: "",
+  type: "",
   data: {},
 });
 const dialogData = ref({
   show: false,
-  title: ui18n.t('sys.upload'),
+  title: ui18n.t("sys.upload"),
   data: {},
   fzshow: false,
-  fzName: '',
+  fzName: "",
   fzList: [],
 });
-const upload = ref(null)
+const upload = ref(null);
 const file = ref(null);
 const audio = ref(null);
+const loadingText = ref("0%");
+
 const accept = ".jpg,.png,.jpeg,.mp4,.wav,.mp3,.shp,.zip";
 const handleClose = () => {
   console.log("handleClose", audio.value);
-  if(audio.value){
-    audio.value.pause()
+  if (audio.value) {
+    audio.value.pause();
   }
 };
 const hanleFzDle = (item) => {
-  ElMessageBox.confirm(ui18n.t('mediaLibrary.tips.deltext'), ui18n.t('sys.dialogTitle'), {
-    confirmButtonText: ui18n.t('sys.enter'),
-    cancelButtonText: ui18n.t('sys.cancel'),
-  }).then(async () => {
+  ElMessageBox.confirm(
+    ui18n.t("mediaLibrary.tips.deltext"),
+    ui18n.t("sys.dialogTitle"),
+    {
+      confirmButtonText: ui18n.t("sys.enter"),
+      cancelButtonText: ui18n.t("sys.cancel"),
+    }
+  ).then(async () => {
     await getfzdel(item);
-    dictIdList.value = dictIdList.value.filter(item1 => item1.id !== item.id)
+    dictIdList.value = dictIdList.value.filter((item1) => item1.id !== item.id);
     initData();
     ElMessage({
       type: "success",
-      message: ui18n.t('mediaLibrary.tips.del'),
+      message: ui18n.t("mediaLibrary.tips.del"),
     });
   });
-  
 
   console.log("upload", file);
 };
-const { photography, modeling } = getUrlData()
+const { photography, modeling } = getUrlData();
 const previewFile = (file) => {
   console.log("previewFile", file);
 };
@@ -327,7 +428,7 @@ const handleEdit = (data) => {
 
   dialogData.value = {
     show: true,
-    title: ui18n.t('mediaLibrary.setGrouping'),
+    title: ui18n.t("mediaLibrary.setGrouping"),
     data,
   };
 };
@@ -337,85 +438,102 @@ const removeFile = () => {
 };
 const handleAdd = () => {
   fileList.value = [];
-  addForm.value.dictId = '';
-  dialogData.value.title = ui18n.t('sys.upload');
+  addForm.value.dictId = "";
+  dialogData.value.title = ui18n.t("sys.upload");
   setTimeout(() => {
-  dialogData.value.show = true;
-  }, 100)
+    dialogData.value.show = true;
+  }, 100);
 };
 const handlefzAdd = async () => {
   let params = {
-    dictName: dialogData.value.fzName,
+    dictName: dialogData.value.fzName?.trim(),
   };
   if (!params.dictName) {
-    return ElMessage.error(ui18n.t('mediaLibrary.tips.placeholderName'));
+    return ElMessage.error(ui18n.t("mediaLibrary.tips.placeholderName"));
   }
   await getaddOrUpdate(params);
   ElMessage({
     type: "success",
-    message: ui18n.t('mediaLibrary.tips.add'),
+    message: ui18n.t("mediaLibrary.tips.add"),
   });
   getFzlist();
-  dialogData.value.fzName = '';
+  dialogData.value.fzName = "";
 };
 const handleAddfz = () => {
   dialogData.value.fzshow = true;
 };
 
 const handleuploadAdd = async () => {
-  console.log('formData', dialogData.value.title);
-  if (dialogData.value.title != ui18n.t('sys.upload')){
-    if (!addForm.value.dictId) return ElMessage.error(ui18n.t('mediaLibrary.tips.dictId'));
+  console.log("formData", dialogData.value.title);
+  if (dialogData.value.title != ui18n.t("sys.upload")) {
+    if (!addForm.value.dictId)
+      return ElMessage.error(ui18n.t("mediaLibrary.tips.dictId"));
     await setFileaddOrUpdate({
       id: dialogData.value.data?.id,
       dictId: addForm.value.dictId,
-    })
+    });
     initData();
-    dialogData.value.show = false
-    dialogData.value.title = ''
+    dialogData.value.show = false;
+    dialogData.value.title = "";
     ElMessage({
       type: "success",
-      message: ui18n.t('mediaLibrary.tips.operate'),
+      message: ui18n.t("mediaLibrary.tips.operate"),
     });
   } else {
     const formData = new FormData();
-    const file = fileList.value && fileList.value[0]
-    if (!file) return ElMessage.error(ui18n.t('mediaLibrary.tips.addUploadErr'));
-    console.log(file, "file");
+    const file = fileList.value && fileList.value[0];
+    if (!file) return ElMessage.error(ui18n.t("common.NoFilesSelected"));
     formData.append("file", file);
     formData.append("dictId", addForm.value.dictId);
-    uploadFile(formData).then((res) => {
-    console.log(res);
-    if (res.status == 1) {
-      initData();
-      dialogData.value.show = false;
-      ElMessage({
-        type: "success",
-        message: ui18n.t('mediaLibrary.tips.uplooadSuccess'),
-      });
-    }else{
-      ElMessage.error(ui18n.t('mediaLibrary.tips.uplooadErr'));
-    }
-  });
+    console.log(file, formData, "formData");
+    const loading = ElLoading.service({
+      lock: true,
+      text: loadingText.value,
+      background: "rgba(0, 0, 0, 0.7)",
+    });
+    uploadFile({ file, dictId: addForm.value.dictId }, (completeProgress) => {
+      console.log("上传进度", completeProgress);
+      // props.progress = completeProgress; //上传过程
+      loadingText.value = completeProgress + "%";
+      loading.setText(loadingText.value)
+    }).then((res) => {
+      loading.close();
+      if (res?.status == 1) {
+        initData();
+        dialogData.value.show = false;
+        ElMessage({
+          type: "success",
+          message: ui18n.t("mediaLibrary.tips.uplooadSuccess"),
+        });
+      } else {
+        ElMessage.error(ui18n.t("mediaLibrary.tips.uplooadErr"));
+      }
+    });
     console.log(formData);
   }
 };
 // 定义方法
 const del = (row) => {
   console.log(file, "file");
-  ElMessageBox.confirm(ui18n.t('mediaLibrary.tips.deltext'), ui18n.t('sys.dialogTitle'), {
-    confirmButtonText: ui18n.t('sys.enter'),
-    cancelButtonText: ui18n.t('sys.cancel'),
-  }).then(async () => {
-    await getdictFiledel({id: row.id});
+  ElMessageBox.confirm(
+    ui18n.t("mediaLibrary.tips.deltext"),
+    ui18n.t("sys.dialogTitle"),
+    {
+      confirmButtonText: ui18n.t("sys.enter"),
+      cancelButtonText: ui18n.t("sys.cancel"),
+    }
+  ).then(async () => {
+    await getdictFiledel({ id: row.id });
     initData();
     ElMessage({
       type: "success",
-      message: ui18n.t('mediaLibrary.tips.del'),
+      message: ui18n.t("mediaLibrary.tips.del"),
     });
   });
 };
-const allList = ref([{ dictName: ui18n.t('mediaLibrary.fileType.0'), id: "0" }])
+const allList = ref([
+  { dictName: ui18n.t("mediaLibrary.fileType.0"), id: "0" },
+]);
 const dictIdList = ref([]);
 const tableData = ref([]);
 const fileList = ref([]);
@@ -460,54 +578,75 @@ const initData = async () => {
     return {
       ...res,
       statusStr:
-        res.status == -1 ? ui18n.t('mediaLibrary.tips.uplooadErr') : res.status == 0 ? ui18n.t('mediaLibrary.tips.uplooad') : ui18n.t('mediaLibrary.tips.uplooadSuccess'),
+        res.status == -1
+          ? ui18n.t("mediaLibrary.tips.uplooadErr")
+          : res.status == 0
+          ? ui18n.t("mediaLibrary.tips.uplooad")
+          : ui18n.t("mediaLibrary.tips.uplooadSuccess"),
       createTime: dayjs(res.createTime).format("YYYY-MM-DD HH:mm:ss"),
     };
   });
   total.value = totalCount;
 };
 const floadileUrl = (record) => {
-  dialogDowm.value.show = true;
   dialogDowm.value.type = record.fileType;
   dialogDowm.value.data = record;
   if (record.fileType == 3) {
     // let url = `/code/index.html?title=${record.fileName}&type=${record.fileFormat}&fileUrl=${record.fileUrl}#/sign-model`;
-    dialogDowm.value.url = getUrlSrc({...record, type: 101});
-    return windowOpen({url, library: true});
-  } else {
-    dialogDowm.value.url = getUrlSrc({type: 102}) +'/'+ record.fileUrl;
+    dialogDowm.value.url = getUrlSrc({ ...record, type: 101 });
+    dialogDowm.value.show = true;
+    // return windowOpen({url: dialogDowm.value.url, library: true});
+  } else if (
+    record.fileType == 2 ||
+    record.fileType == 1 ||
+    record.fileType == 0
+  ) {
+    dialogDowm.value.url = getUrlSrc({ type: 102 }) + "/" + record.fileUrl;
+    dialogDowm.value.show = true;
     // return windowOpen({url: record.fileUrl, library: true});
+  } else {
+    hanleDown();
   }
 };
 const handleExceed = (files) => {
-  const file = files[0]
-  if(!beforeUpload(file)) {
+  const file = files[0];
+  if (!beforeUpload(file)) {
     return false;
   }
-  upload.value && upload.value.clearFiles()
-  file.uid = genFileId()
-  upload.value && upload.value.handleStart(file)
+  upload.value && upload.value.clearFiles();
+  file.uid = genFileId();
+  upload.value && upload.value.handleStart(file);
 };
 const beforeUpload = (file) => {
-        console.log('beforeUpload', file);
-        const filetype = file.name.substring(file.name.lastIndexOf('.') + 1).toLowerCase();
-        const isExcel = ['jpg', 'jpg', 'png', 'jpeg', 'mp4', 'wav', 'mp3', 'shp', 'zip'].includes(
-          filetype,
-        ); // 调用上面代码
-        if (!isExcel) {
-          ElMessage.error(ui18n.t('mediaLibrary.tips.uplooadfiletype'));
-          fileList.value = [];
-          return false;
-        }
-        const isLt10M = file.size / 1024 / 1024 < 2000;
-        if (!isLt10M) {
-          fileList.value = [];
-          ElMessage.error(ui18n.t('mediaLibrary.tips.uplooadSize'));
-          return false;
-        }
-        fileList.value = [file];
-        return true;
-      };
+  console.log("beforeUpload", file);
+  const filetype = file.name
+    .substring(file.name.lastIndexOf(".") + 1)
+    .toLowerCase();
+  const isExcel = [
+    "jpg",
+    "jpg",
+    "png",
+    "jpeg",
+    "mp4",
+    "wav",
+    "mp3",
+    "shp",
+    "zip",
+  ].includes(filetype); // 调用上面代码
+  if (!isExcel) {
+    ElMessage.error(ui18n.t("mediaLibrary.tips.uplooadfiletype"));
+    fileList.value = [];
+    return false;
+  }
+  const isLt10M = file.size / 1024 / 1024 < 2000;
+  if (!isLt10M) {
+    fileList.value = [];
+    ElMessage.error(ui18n.t("mediaLibrary.tips.uplooadSize"));
+    return false;
+  }
+  fileList.value = [file];
+  return true;
+};
 // 监听搜索表单变化
 watch(searchForm, () => {
   currentPage.value = 1;
@@ -528,12 +667,12 @@ const handleCurrentChange = (newPage) => {
 // 下载资源
 const hanleDown = () => {
   const item = dialogDowm.value.data;
-  const a = document.createElement('a');
+  const a = document.createElement("a");
   a.href = dialogDowm.value.url;
   a.download = `${item.name}.${item.fileFormat}`; // 下载后的文件名
   a.click();
   a.remove();
-}
+};
 // 处理搜索按钮点击
 const handleSearch = () => {
   currentPage.value = 1;
@@ -584,24 +723,24 @@ initData();
   // .el-table{
   //   background-color: #535353;
   // }
-  .grouping{
+  .grouping {
     padding: 7px 10px;
     background-color: var(--el-fill-color-blank);
     box-shadow: 0 0 0 1px var(--el-border-color);
-    border-radius:   0  4px  4px 0 ;
-      opacity: 0.7;
-      cursor: pointer;
-    &:hover{
-      box-shadow: 0 0 0 1px #6C6E72;
+    border-radius: 0 4px 4px 0;
+    opacity: 0.7;
+    cursor: pointer;
+    &:hover {
+      box-shadow: 0 0 0 1px #6c6e72;
       opacity: 1;
     }
   }
-  .groupingSelcet{
-    .el-select__wrapper{
-      border-radius:4px  0 0  4px;
+  .groupingSelcet {
+    .el-select__wrapper {
+      border-radius: 4px 0 0 4px;
     }
   }
-  .el-form--inline .el-form-item{
+  .el-form--inline .el-form-item {
     margin-right: 20px;
   }
   // .el-form-item, .el-select__wrapper{
@@ -648,34 +787,34 @@ initData();
   //   .el-table .success-row {
   //     --el-table-tr-bg-color: var(--el-color-success-light-9);
   //   }
-  .itemTitle{
+  .itemTitle {
     width: 100%;
     margin: 0 0 10px 0;
-    .itemTitleList{
+    .itemTitleList {
       margin-top: 10px;
-      background: rgba(255,255,255,0.1);
+      background: rgba(255, 255, 255, 0.1);
       border-radius: 4px 4px 4px 4px;
-      border: 1px solid rgba(255,255,255,0.2);
+      border: 1px solid rgba(255, 255, 255, 0.2);
       max-height: 240px;
       overflow-y: scroll;
       ::-webkit-scrollbar {
         display: none; /* Chrome Safari */
       }
     }
-    .itemTitle-list{
+    .itemTitle-list {
       padding-left: 10px;
       line-height: 34px;
       display: flex;
       justify-content: space-between;
       position: relative;
       cursor: pointer;
-      .name{
+      .name {
         width: 350px;
         overflow: hidden; //超出的文本隐藏
         text-overflow: ellipsis; //溢出用省略号显示
         white-space: nowrap; //溢出不换行
       }
-      .del{
+      .del {
         color: red;
         width: 40px;
 
@@ -684,16 +823,15 @@ initData();
     }
   }
 }
-.showContent{
-  iframe{
+.showContent {
+  iframe {
     body:-webkit-full-page-media {
       background-color: none;
     }
   }
 }
-.el-pager li.is-active{
+.el-pager li.is-active {
   border: 1px solid var(--el-color-primary);
   background-color: none;
-
 }
 </style>

+ 1 - 1
src/view/originalPhoto/addCaseFile.vue

@@ -53,7 +53,7 @@
         :before-remove="removeFile"
       >
         <div type="primary" :disabled="!!file">
-          <div>点击或拖拽文件上传</div>
+          <div>点击文件上传</div>
           <div class="">{{ size }}以内的{{ formatDesc }}</div>
         </div>
         <template v-slot:file="{ file }: { file: UploadFile }">

+ 1 - 1
src/view/originalPhoto/addLibrary.vue

@@ -45,7 +45,7 @@
         :before-remove="removeFile"
       >
         <div type="primary" :disabled="!!file">
-          <div>点击或拖拽文件上传 {{ size }}</div>
+          <div>点击文件上传 {{ size }}</div>
           <div class="">以内的{{ formatDesc }}</div>
         </div>
         <template v-slot:file="{ file }: { file: UploadFile }">

+ 2 - 2
src/view/other/index.vue

@@ -17,7 +17,7 @@
         :before-remove="removeFile"
       >
         <div type="primary" :disabled="!!file">
-          <div>点击或拖拽文件上传</div>
+          <div>点击文件上传</div>
           <div class="">支持{{ fileInfo.DrawFormatDesc }}格式上传</div>
         </div>
       </el-upload>
@@ -38,7 +38,7 @@
         :before-remove="removeFile"
       >
         <div type="primary" :disabled="!!file">
-          <div>点击或拖拽文件上传</div>
+          <div>点击文件上传</div>
           <div class="">支持{{ fileInfo.DrawFormatDesc }}格式上传</div>
         </div>
       </el-upload>

+ 11 - 5
src/view/vrmodel/index.vue

@@ -56,7 +56,7 @@ import {
 import comSelect from "@/components/company-select/index.vue";
 import List from "./list.vue";
 import SceneContent from "./sceneContent.vue";
-import { ElMessage } from "element-plus";
+import { ElMessage, ElMessageBox } from "element-plus";
 import ModelContent from "./modelContent.vue";
 import { useScenePaggingParams } from "./pagging";
 import { computed, ref, onMounted } from "vue";
@@ -92,7 +92,6 @@ let querys = getAllParams()
 console.log("getAllParams", route.name, querys.ga);
 if(querys.ga === 'true' && route.name != 'scene'){
   router.replace('/home/'+caseId.value as number)
-
 }
 if(querys.ga === 'false' && route.name != 'homes'){
   router.replace('/homes/'+caseId.value as number)
@@ -121,10 +120,17 @@ async function handlegotoelT(record) {
   //   ElMessage.error("无法移除,场景已加入多元融合,请进入多元融合移除场景后再试");
   //   return
   // }
-  if (record.inFusion || (await confirm(ui18n.t('sceneHome.ycTips')))) {
+  ElMessageBox.confirm(ui18n.t('sceneHome.ycTips'), ui18n.t('sys.dialogTitle'), {
+    confirmButtonText: ui18n.t('sys.enter'),
+    cancelButtonText: ui18n.t('sys.cancel'),
+  }).then(async () => {
     list.value = list.value.filter((item) => item.id !== record.id);
     submitForm();
-  }
+  });
+  // if (record.inFusion || (await confirm(ui18n.t('sceneHome.ycTips')))) {
+  //   list.value = list.value.filter((item) => item.id !== record.id);
+  //   submitForm();
+  // }
   // console.log("handleActive", params);
 }
 async function submitForm() {
@@ -138,7 +144,7 @@ async function submitForm() {
   setCaseaddOrUpdate(apiData).then((res) => {
     geiList();
     ElMessage({
-      message: ui18n.t('program.resStatus.200'),
+      message: ui18n.t('resStatus.200'),
       type: "success",
     });
   }).catch((errr) => {

+ 1 - 1
src/view/vrmodel/sceneContent.vue

@@ -187,7 +187,7 @@ watchEffect(() => {
   // console.log('watchEffect', props.pagging.state.table.rows);
 }, { flush: 'post' });
 const renderStatus = (status) => {
-  return QuoteSceneStatusDesc[status] || "未知状态";
+  return QuoteSceneStatusDesc[status];
 };
 const changeSelection = async (selectScenes) => {
   if (changIng) return;

+ 1 - 1
vite.config.ts

@@ -61,7 +61,7 @@ export default defineConfig({
         rewrite: (path) => path.replace(new RegExp(`^/api`), "/fusion"),
       },
       "/dev-code": {
-        // target: "https://localhost:7173/",
+        // target: "http://192.168.9.171:9200",
         target: dev
           ? "https://test-mix3d.4dkankan.com/code"
           : "https://mix3d.4dkankan.com/code",

+ 39 - 0
yarn.lock

@@ -948,6 +948,19 @@
   resolved "https://mirrors.cloud.tencent.com/npm/@floating-ui/utils/-/utils-0.2.8.tgz#21a907684723bbbaa5f0974cf7730bd797eb8e62"
   integrity sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==
 
+"@googlemaps/js-api-loader@^1.16.2":
+  version "1.16.8"
+  resolved "https://registry.npmmirror.com/@googlemaps/js-api-loader/-/js-api-loader-1.16.8.tgz#1595a2af80ca07e551fc961d921a2437d1cb3643"
+  integrity sha512-CROqqwfKotdO6EBjZO/gQGVTbeDps5V7Mt9+8+5Q+jTg5CRMi3Ii/L9PmV3USROrt2uWxtGzJHORmByxyo9pSQ==
+
+"@googlemaps/markerclusterer@^2.4.0":
+  version "2.5.3"
+  resolved "https://registry.npmmirror.com/@googlemaps/markerclusterer/-/markerclusterer-2.5.3.tgz#9f891ce7e8e161775f3a3e2c9f66956810284591"
+  integrity sha512-x7lX0R5yYOoiNectr10wLgCBasNcXFHiADIBdmn7jQllF2B5ENQw5XtZK+hIw4xnV0Df0xhN4LN98XqA5jaiOw==
+  dependencies:
+    fast-deep-equal "^3.1.3"
+    supercluster "^8.0.1"
+
 "@intlify/core-base@11.1.1":
   version "11.1.1"
   resolved "https://registry.npmmirror.com/@intlify/core-base/-/core-base-11.1.1.tgz#2ab2f21fac40a5a2ceb190e9cbf6c23393e105a2"
@@ -1712,6 +1725,11 @@ esutils@^2.0.2:
   resolved "https://registry.npmmirror.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
   integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==
 
+fast-deep-equal@^3.1.3:
+  version "3.1.3"
+  resolved "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
+  integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
+
 fast-glob@^3.3.2:
   version "3.3.2"
   resolved "https://mirrors.cloud.tencent.com/npm/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129"
@@ -1893,6 +1911,11 @@ json5@^2.2.3:
   resolved "https://registry.npmmirror.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
   integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
 
+kdbush@^4.0.2:
+  version "4.0.2"
+  resolved "https://registry.npmmirror.com/kdbush/-/kdbush-4.0.2.tgz#2f7b7246328b4657dd122b6c7f025fbc2c868e39"
+  integrity sha512-WbCVYJ27Sz8zi9Q7Q0xHC+05iwkm3Znipc2XTlrnJbsHMYktW4hPhXUE8Ys1engBrvffoSCqbil1JQAa7clRpA==
+
 kolorist@^1.8.0:
   version "1.8.0"
   resolved "https://mirrors.cloud.tencent.com/npm/kolorist/-/kolorist-1.8.0.tgz#edddbbbc7894bc13302cdf740af6374d4a04743c"
@@ -2277,6 +2300,13 @@ source-map@^0.6.0:
   resolved "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
   integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
 
+supercluster@^8.0.1:
+  version "8.0.1"
+  resolved "https://registry.npmmirror.com/supercluster/-/supercluster-8.0.1.tgz#9946ba123538e9e9ab15de472531f604e7372df5"
+  integrity sha512-IiOea5kJ9iqzD2t7QJq/cREyLHTtSmUT6gQsweojg9WH2sYJqZK9SswTu6jrscO6D1G5v5vYZ9ru/eq85lXeZQ==
+  dependencies:
+    kdbush "^4.0.2"
+
 supports-preserve-symlinks-flag@^1.0.0:
   version "1.0.0"
   resolved "https://registry.npmmirror.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
@@ -2477,6 +2507,15 @@ vue-tsc@^1.8.5:
     "@vue/language-core" "1.8.27"
     semver "^7.5.4"
 
+vue3-google-map@^0.21.1:
+  version "0.21.1"
+  resolved "https://registry.npmmirror.com/vue3-google-map/-/vue3-google-map-0.21.1.tgz#5907aa34e55b61fe520cc324fe4a37be70bfeb10"
+  integrity sha512-5CA/1GLei4thQgXhNVBSBcJvylOhcrk2a/ISdaticdn0/Ipsw0/zHyGL7tp98eAajPwiwFOnl9epKkhdMfv8sA==
+  dependencies:
+    "@googlemaps/js-api-loader" "^1.16.2"
+    "@googlemaps/markerclusterer" "^2.4.0"
+    fast-deep-equal "^3.1.3"
+
 vue@^3.3.4:
   version "3.5.13"
   resolved "https://mirrors.cloud.tencent.com/npm/vue/-/vue-3.5.13.tgz#9f760a1a982b09c0c04a867903fc339c9f29ec0a"