Jelajahi Sumber

Merge branch 'xj' of http://192.168.0.115:3000/bill/public-fuse into xj

bill 1 tahun lalu
induk
melakukan
213c20a55c

+ 3 - 3
src/app/fire/constant.ts

@@ -2,10 +2,10 @@ import { AppConstant } from "../";
 import banner from "@/assets/image/fireBanner.png";
 import ico from "@/assets/image/fire.ico";
 import { fireDeptId } from "@/constant/appDeptId";
-
+// 新疆消防救援总队火灾现场重建平台
 export const appConstant: AppConstant = {
-  title: "新疆消防救援总队火灾现场重建平台",
-  desc: "",
+  title: "消防火调三维远程勘验平台",
+  desc: "Three-dimensional remote prospecting platform for fire scenes",
   ico,
   banner,
   name: "fire",

+ 42 - 38
src/core/Scene.js

@@ -4,23 +4,12 @@ import Player from "./player/Player.js";
 import BoxManager from "./box/BoxManager.js";
 import { Mitt } from "./mitt.js";
 import testData from "./save.json";
+import { dataURItoBlob, saveFile } from "./utils/utils.js";
 const stats = new Stats();
 
 function sleep(ms) {
   return new Promise((resolve) => setTimeout(resolve, ms));
 }
-const saveFile = function (strData, filename) {
-  var link = document.createElement("a");
-  if (typeof link.download === "string") {
-    document.body.appendChild(link); //Firefox requires the link to be in the body
-    link.download = filename;
-    link.href = strData;
-    link.click();
-    document.body.removeChild(link); //remove the link when done
-  } else {
-    location.replace(uri);
-  }
-};
 
 export default class Scene extends Mitt {
   constructor(domElement) {
@@ -36,7 +25,7 @@ export default class Scene extends Mitt {
     this.defaultZoom = 250;
     this.initCamPView = new THREE.Vector3();
     this.initCamRView = new THREE.Vector3();
-
+    this.blobScreens = [];
     this.inited = false;
 
     this.init = () => {
@@ -152,10 +141,12 @@ export default class Scene extends Mitt {
   toVertical = () => {};
 
   lockView(open) {
-    if (open) {
+    if (!open) {
       this.player.floorplanControls.enablePan = true;
+      this.player.floorplanControls.enableZoom = true;
     } else {
       this.player.floorplanControls.enablePan = false;
+      this.player.floorplanControls.enableZoom = false;
     }
   }
   setMode(mode) {
@@ -197,11 +188,17 @@ export default class Scene extends Mitt {
   editing(item) {
     this.player.editing(item);
   }
-
-  screenshot(x, index) {
+  endScreenshot() {
+    this.lockView(false);
+    this.blobScreens = [];
+    this.player.floorplanControls.reset();
+    this.onResize();
+    this.renderer.setSize(this.width, this.height);
+  }
+  screenshot(x, zoom) {
     var imgData, imgNode;
     const times = 4;
-    this.orthCamera.zoom = this.defaultZoom;
+    this.orthCamera.zoom = zoom || this.defaultZoom;
     this.scene.position.x = x || 0;
 
     this.renderer.setSize(this.width * times, this.height * times);
@@ -213,50 +210,57 @@ export default class Scene extends Mitt {
     this.renderer.render(this.scene, this.orthCamera, null, false);
 
     const dataURL = this.renderer.domElement.toDataURL("image/jpeg");
-
-    saveFile(dataURL, `${index}.jpg`);
+    this.blobScreens.push(dataURItoBlob(dataURL));
+    // saveFile(dataURL, `${index}.jpg`);
     this.onResize(this.width, this.height);
   }
 
-  test() {
+  exportScreenshot() {
+    this.player.floorplanControls.reset();
+    this.lockView(true);
+    this.setMode(0);
+    // await sleep(500);
     this.orthCamera.zoom = this.defaultZoom;
     const object = this.boxManager.model;
-    let total;
-    if (this.sceneType === 2) {
-      total = this.boxManager.imgList.length;
-    } else {
-      total = this.boxManager.imgList.length / 2;
+    const total = object.children.length;
+    if (total === 0) {
+      return;
+      console.error("没数据");
     }
 
     object.updateMatrixWorld();
     this.orthCamera.updateProjectionMatrix();
     const boundingBox = new THREE.Box3().setFromObject(object);
-
     // 计算宽度、高度和深度
     const width = boundingBox.max.x - boundingBox.min.x;
     const one = width / total;
     let slides = Math.floor(total / 3);
+    // debugger;
     console.log("slides", slides);
-    if (slides > 1) {
+    if (slides >= 1) {
       for (var i = 0; i <= slides; i++) {
         (function (index, that) {
           setTimeout(function () {
             const offset = -(one * 3 * index);
             console.log("Iteration:", offset);
-            that.screenshot(offset, index);
-
+            that.screenshot(offset);
             console.log(`Width: ${offset}`);
-          }, index * 1000);
+            if (index === slides) {
+              console.log("last");
+              that.scene.position.x = 0;
+              that.emit("submitScreenshot");
+            }
+          }, index * 500);
         })(i, this); // 传递当前迭代的索引i给setTimeout的回调函数
       }
-    }
-  }
-
-  test1() {
-    const object = this.boxManager.model;
-    for (var i = 0; i <= object.children.length; i++) {
-      console.log(object.children[i]);
-      this.scene.lookAt(this.boxManager.model);
+    } else {
+      // 只有一个或0个
+      if (total >= 1 && total <= 3) {
+        console.log("total", total);
+        this.player.floorplanControls.reset();
+        this.screenshot(-0.3, 227);
+        this.emit("submitScreenshot");
+      }
     }
   }
 

+ 11 - 10
src/core/box/VerticalBox.js

@@ -53,16 +53,17 @@ export default class VerticalBox extends THREE.Group {
         let imgRatio = texture.image.width / texture.image.height;
         let planeRatio = 1.5 / 2;
         texture.matrixAutoUpdate = false;
+        //放开所有uv的比例
         if (planeRatio > imgRatio) {
-          texture.matrix.setUvTransform(
-            0,
-            0,
-            planeRatio / imgRatio,
-            1,
-            0,
-            0.5,
-            0.5
-          );
+          // texture.matrix.setUvTransform(
+          //   0,
+          //   0,
+          //   planeRatio / imgRatio,
+          //   1,
+          //   0,
+          //   0.5,
+          //   0.5
+          // );
         } else {
           // debugger
           // texture.matrix.setUvTransform(
@@ -77,7 +78,7 @@ export default class VerticalBox extends THREE.Group {
         }
 
         texture.colorSpace = THREE.SRGBColorSpace;
-     
+
         // texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
         // texture.repeat.set(2, 2); // 纹理平铺两次
         // texture.offset.set(0.5, 0.5); // 纹理偏移到中心

+ 56 - 3
src/core/box/object/ImgLabelBox.js

@@ -1,6 +1,48 @@
 import * as THREE from "three";
+import { TriangleBlurShader } from "three/addons/shaders/TriangleBlurShader.js";
 import TouchEdge from "./TouchEdge";
 
+function makeTriangleBlurShader(iterations = 10) {
+  // Remove texture, because texture is a reserved word in WebGL 2
+  const { texture, ...uniforms } = TriangleBlurShader.uniforms;
+
+  const TriangleBlurShader2 = {
+    ...TriangleBlurShader,
+
+    name: "TriangleBlurShader2",
+
+    uniforms: {
+      ...uniforms,
+
+      // Replace texture with blurTexture for WebGL 2
+      blurTexture: { value: null },
+    },
+  };
+
+  // Replace texture with blurTexture for WebGL 2
+  TriangleBlurShader2.fragmentShader =
+    TriangleBlurShader2.fragmentShader.replace(
+      "uniform sampler2D texture;",
+      "uniform sampler2D blurTexture;"
+    );
+  TriangleBlurShader2.fragmentShader =
+    TriangleBlurShader2.fragmentShader.replace(
+      "texture2D( texture",
+      "texture2D( blurTexture"
+    );
+
+  // Make iterations configurable.
+  TriangleBlurShader2.fragmentShader =
+    TriangleBlurShader2.fragmentShader.replace(
+      "#define ITERATIONS 10.0",
+      "#define ITERATIONS " + iterations + ".0"
+    );
+
+  console.log("shader:", TriangleBlurShader2.fragmentShader);
+
+  return TriangleBlurShader2;
+}
+
 export default class ImgLabel extends THREE.Mesh {
   constructor(texture, matLine, isHorizontal = true) {
     let width, height, p;
@@ -31,6 +73,17 @@ export default class ImgLabel extends THREE.Mesh {
     //   transparent: true,
     // });
 
+    // const shader = makeTriangleBlurShader(12);
+
+    // const blurMaterial = new THREE.ShaderMaterial({
+    //   vertexShader: shader.vertexShader,
+    //   fragmentShader: shader.fragmentShader,
+    //   uniforms: THREE.UniformsUtils.clone(shader.uniforms),
+    // });
+    // // console.log("blurMaterial", blurMaterial.uniforms);
+    // blurMaterial.uniforms.blurTexture.value = texture;
+    // blurMaterial.uniforms.delta.value = new THREE.Vector2(0.5, 0.9);
+
     const bg = new THREE.MeshBasicMaterial({
       color: 0xf2f2f2,
       transparent: false,
@@ -39,14 +92,14 @@ export default class ImgLabel extends THREE.Mesh {
     super(g, bg);
 
     let imgRatio = texture.image.width / texture.image.height;
-
-    const imageG = new THREE.PlaneGeometry(width, width / imgRatio);
+    const imgHeight = width / imgRatio >= 2 ? 2 : width / imgRatio;
+    const imageG = new THREE.PlaneGeometry(width, imgHeight);
 
     imageG.rotateX(-Math.PI / 2);
 
     const im = new THREE.MeshBasicMaterial({
       map: texture,
-      transparent: false,
+      transparent: true,
     });
     const imageMesh = new THREE.Mesh(imageG, im);
 

+ 5 - 2
src/core/box/object/marker.js

@@ -7,7 +7,7 @@ const m = new THREE.MeshBasicMaterial({
 });
 
 export default class Marker extends THREE.Mesh {
-  constructor(startPoint) {
+  constructor(startPoint, imageId) {
     const g = new THREE.PlaneGeometry(0.15, 0.15);
     g.rotateX(-Math.PI / 2);
     super(g, m);
@@ -17,7 +17,10 @@ export default class Marker extends THREE.Mesh {
     this.rotation.y = 0;
     this.position.y = 5;
     this.position.z -= 0.02;
-    this.userData = a.toArray();
+    this.userData = {
+      imageId: imageId || null,
+      point: a.toArray(),
+    };
     this.visible = true;
     this.scale.set(1, 1, 1);
     this.position.y += 0.5;

+ 53 - 12
src/core/player/Player.js

@@ -49,6 +49,7 @@ export default class Player {
     this.activeEdges = [];
     this.renderSymbols = [];
     this.renderTexts = [];
+
     this.matLine = null;
     this.lineColor = 0xe44d54;
     // 1是画线,2是标方向, 3符号, 4文本
@@ -312,7 +313,7 @@ export default class Player {
           );
           lasPos.unproject(this.orthCamera);
           lasPos.y = 5;
-          const marker = new Marker(lasPos);
+          const marker = new Marker(lasPos, imageId);
 
           const activeMarkeritem = {
             id: imageId,
@@ -541,14 +542,14 @@ export default class Player {
     }
   }
 
-  insertActiveMarker(item) {
-    const exist = this.activeEdges.find((s) => item.id === s.id);
-    if (exist) {
-      exist.dir = [...new Set([...exist.dir, ...item.dir])];
-    } else {
-      this.activeEdges.push(item);
-    }
-  }
+  // insertActiveMarker(item) {
+  //   const exist = this.activeEdges.find((s) => item.id === s.id);
+  //   if (exist) {
+  //     exist.dir = [...new Set([...exist.dir, ...item.dir])];
+  //   } else {
+  //     this.activeEdges.push(item);
+  //   }
+  // }
 
   insertrenderTexts(item) {
     const index = this.renderTexts.findIndex((s) => item.id === s.id);
@@ -725,7 +726,7 @@ export default class Player {
         hor_markers.forEach((pos) => {
           console.log("pos");
           const p = new THREE.Vector3().fromArray(pos.point);
-          const marker = new Marker(p);
+          const marker = new Marker(p, pos.id);
           this.scene.scene.add(marker);
         });
       }
@@ -846,14 +847,22 @@ export default class Player {
     this.scene.clearDrawScene();
     this.syncDrawData();
   }
+
   //单多张图片删除时要删除相关数据
   deleteImageDataByIds(ids) {
+    setTimeout(() => {
+      console.warn("单多张图片删除时要删除相关数据", ids);
+      this.clear();
+      this.scene.emit("autoSave");
+      // this.t_deleteImageDataByIds(ids);
+    }, 500);
+  }
+  t_deleteImageDataByIds(ids) {
     ids.forEach((id) => {
       const makerIndex = this.renderMarkers.findIndex((item) => item.id === id);
       if (makerIndex > -1) {
         this.renderMarkers.splice(makerIndex, 1);
       }
-
       const lines = this.renderLines.filter(
         (item) => item.imgId === id || item.startId === id
       );
@@ -887,11 +896,43 @@ export default class Player {
       });
       console.log("lines", lines);
     });
-    this.syncDrawData();
+
     setTimeout(() => {
+      this.syncDrawData();
       this.scene.emit("autoSave");
     }, 2500);
   }
+
+  checkDeleteing() {
+    const makers = this.scene.scene.children.filter((obj) =>
+      String(obj.name).includes("marker_")
+    );
+    Array.from(makers).forEach((marker) => {
+      const { imageId, point } = marker.userData;
+      const image = this.scene.boxManager.imgList.find(
+        (item) => item.userData === imageId
+      );
+      if (image) {
+        const nPoint = new THREE.Vector3().fromArray(point);
+        nPoint.project(this.orthCamera);
+        console.log("nPoint", nPoint, point);
+
+        this.raycaster.setFromCamera(nPoint, this.orthCamera);
+        const intersects = this.raycaster.intersectObjects(
+          this.scene.boxManager.imgList,
+          false
+        );
+
+        if (image.userData !== intersects[0].object.userData) {
+          console.log("相交不正确");
+          this.scene.scene.remove(marker);
+        }
+      }
+
+      // const
+    });
+  }
+
   update = () => {
     if (this.floorplanControls.enabled) {
       this.floorplanControls && this.floorplanControls.update();

+ 39 - 0
src/core/utils/utils.js

@@ -0,0 +1,39 @@
+
+
+export function dataURItoBlob(dataURI) {
+  // convert base64 to raw binary data held in a string
+  // doesn't handle URLEncoded DataURIs - see SO answer #6850276 for code that does this
+  var byteString = atob(dataURI.split(",")[1]);
+
+  // separate out the mime component
+  var mimeString = dataURI.split(",")[0].split(":")[1].split(";")[0];
+
+  // write the bytes of the string to an ArrayBuffer
+  var ab = new ArrayBuffer(byteString.length);
+
+  // create a view into the buffer
+  var ia = new Uint8Array(ab);
+
+  // set the bytes of the buffer to the correct values
+  for (var i = 0; i < byteString.length; i++) {
+    ia[i] = byteString.charCodeAt(i);
+  }
+
+  // write the ArrayBuffer to a blob, and you're done
+  var blob = new Blob([ab], { type: mimeString });
+  return blob;
+}
+
+export const saveFile = function (strData, filename) {
+  var link = document.createElement("a");
+  if (typeof link.download === "string") {
+    document.body.appendChild(link); //Firefox requires the link to be in the body
+    link.download = filename;
+    link.href = strData;
+    link.click();
+    document.body.removeChild(link); //remove the link when done
+  } else {
+    location.replace(uri);
+  }
+};
+

+ 5 - 1
src/request/config.ts

@@ -28,6 +28,7 @@ import {
   cameraVersionUpload,
   cameraVersionAppUpload,
   getCaseHasDownloadProcess,
+  ffmpegMergeImage
 } from "./urls";
 
 // 不需要登录就能请求的接口
@@ -44,7 +45,9 @@ export const notLoginUrls = [
   getAttachListByPsw,
 ];
 // 需要用表单提交的数据
-export const fromUrls: string[] = [];
+export const fromUrls: string[] = [
+  // ffmpegMergeImage
+];
 // 带文件的请求
 export const fileUrls = [
   uploadAttachFile,
@@ -56,6 +59,7 @@ export const fileUrls = [
   saveCaseFileInfo,
   cameraVersionUpload,
   cameraVersionAppUpload,
+  ffmpegMergeImage
 ];
 // 需要限定卫GET请求方式的url
 export const GetUrls = [getRoleList, getCompanyList];

+ 10 - 2
src/request/index.ts

@@ -26,7 +26,7 @@ export type AuthHook = () => {
   clear: () => void;
 };
 export const setAuthHook = (hook: AuthHook) => (getAuth = hook);
-let getAuth: AuthHook = () => ({ token: "", userId: "0", clear: () => {} });
+let getAuth: AuthHook = () => ({ token: "", userId: "0", clear: () => { } });
 
 axios.defaults.baseURL = baseURL;
 
@@ -74,7 +74,15 @@ axios.interceptors.request.use(async (config) => {
     const fromData = new FormData();
 
     Object.keys(config.data).forEach((key) => {
-      fromData.append(key, config.data[key]);
+      if (key === 'files') {
+        Array.from(config.data[key]).forEach(file => {
+          fromData.append('files', file as any as File);
+        })
+      } else {
+        fromData.append(key, config.data[key]);
+      }
+
+
     });
     config.data = fromData;
     config.headers["Content-Type"] = "multipart/form-data";

+ 3 - 0
src/request/urls.ts

@@ -267,3 +267,6 @@ export const cameraVersionAppUpload = `/fusion-xj/cameraVersionApp/addAndUpload`
 export const cameraVersionAppUpdate = `/fusion-xj/cameraVersionApp/update`;
 export const cameraVersionAppDelete = `/fusion-xj/cameraVersionApp/delete`;
 export const cameraVersionAppList = `/fusion-xj/cameraVersionApp/list`;
+
+//相片合成
+export const ffmpegMergeImage = `/fusion-xj/caseImg/ffmpegImage`;

+ 5 - 1
src/store/case.ts

@@ -20,7 +20,8 @@ import {
   caseExtractDetailExport,
   copyExample,
   saveCaseImgTag,
-  getCaseImgTag
+  getCaseImgTag,
+  ffmpegMergeImage
 } from "@/request";
 import { ModelScene, QuoteScene, Scene, SceneType } from "./scene";
 import { CaseFile } from "./caseFile";
@@ -152,3 +153,6 @@ export const saveCaseImgTagData = (params: any) =>
 
 export const getCaseImgTagData = (caseId: number) =>
   axios.get(getCaseImgTag, { params: { caseId } });
+
+
+export const submitMergePhotos = (data) => axios.post(ffmpegMergeImage, { ...data })

+ 18 - 6
src/view/case/photos/draggable.vue

@@ -45,6 +45,7 @@ import { caseImgList, CaseImg, caseDel, caseUpdateSort } from "@/store/case";
 import { VueDraggable } from "vue-draggable-plus";
 import { openErrorMsg } from "@/request/errorMsg.js";
 import { addCaseImgFile } from "../quisk";
+import { ElMessage, ElMessageBox } from "element-plus";
 // import { IconRabbish } from '@element-plus/icons-vue'
 const props = defineProps({ sortType: Boolean, caseId: Number });
 const emit = defineEmits<{
@@ -102,12 +103,23 @@ async function getList() {
   list.value = lists.data;
   emit("changeList", list.value);
 }
-function handleDet(index: Number, id: Number) {
-  caseDel(id).then((res) => {
-    emit("delImage", [id]);
-    list.value.splice(index, 1);
-    emit("changeList", list.value);
-  });
+async function handleDet(index: Number, id: Number) {
+  const res = await ElMessageBox.confirm(
+    "删除图像后会重新排版并重置标记数据,是否继续?",
+    "温馨提示",
+    {
+      confirmButtonText: "确定",
+      cancelButtonText: "取消",
+      type: "default",
+    }
+  );
+  if (res) {
+    caseDel(id).then((res) => {
+      emit("delImage", [id]);
+      list.value.splice(index, 1);
+      emit("changeList", list.value);
+    });
+  }
 }
 async function handleEdit(params) {
   await addCaseImgFile({

+ 38 - 2
src/view/case/photos/index.vue

@@ -40,6 +40,7 @@
         <el-button @click="handleSymbol">符号</el-button>
         <el-button @click="handleText">文本</el-button>
         <el-button @click="handleSave" type="success">保存</el-button>
+        <el-button @click="handleExport" type="success">导出</el-button>
 
         <el-button @click="handleClear" v-if="hasDrawData" type="warning"
           >清空</el-button
@@ -68,11 +69,15 @@ import { Swiper, SwiperSlide } from "swiper/vue";
 import "swiper/css";
 // import { addCaseFile } from "@/store/caseFile";
 import { addCaseImgFile, addCaseImgFileAll } from "../quisk";
-import { saveCaseImgTagData, getCaseImgTagData } from "@/store/case";
+import {
+  saveCaseImgTagData,
+  getCaseImgTagData,
+  submitMergePhotos,
+} from "@/store/case";
 import Scene from "@/core/Scene.js";
 import draggable from "./draggable.vue";
 import edit from "./edit.vue";
-
+import saveAs from "@/util/file-serve";
 import { ElMessage, ElMessageBox } from "element-plus";
 
 const props = defineProps({ caseId: Number });
@@ -199,6 +204,8 @@ const renderCanvas = () => {
     });
     hasDrawData.value = hasData;
     console.log("sync", data, hasData);
+    // editing.value.data = data;
+    loadedDrawData.value = data;
   });
   scene.on("edit", (editData) => {
     console.log("editData", editData);
@@ -210,6 +217,30 @@ const renderCanvas = () => {
     console.log("autoSave");
     handleAutoSave();
   });
+  scene.on("submitScreenshot", () => {
+    if (window.scene) {
+      const params = {
+        files: window.scene.blobScreens.map(
+          (b, index) => new File([b], `${Date.now()}-${index}.jpg`)
+        ),
+        caseId: caseId.value,
+      };
+
+      setTimeout(async () => {
+        try {
+          const res = await submitMergePhotos(params);
+          console.log("res", res);
+          const { data, code } = res;
+          if (data && data.imgUrl) {
+            saveAs(data.imgUrl, `${props.caseId}.jpg`);
+          }
+          window.scene.endScreenshot();
+        } catch (error) {
+          window.scene.endScreenshot();
+        }
+      }, 500);
+    }
+  });
 };
 const onSwiper = (swiper) => {
   console.log("onSwiper");
@@ -342,6 +373,11 @@ const handleImageDel = (ids) => {
     window.scene.deleteImageDataByIds(ids);
   }
 };
+const handleExport = () => {
+  if (window.scene) {
+    window.scene.exportScreenshot();
+  }
+};
 
 onMounted(() => {
   renderCanvas();