闫增涛
2025-06-12 8c96a1d16d1d95101938670b287e39130fd5e086
模型预览
4个文件已修改
2个文件已添加
365 ■■■■■ 已修改文件
package-lock.json 68 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
package.json 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/previewModelDialog.vue 134 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/showModel.vue 109 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/model/children/landerModel.vue 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/simulation/testSimulation/index.vue 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
package-lock.json
@@ -15,6 +15,9 @@
        "moment": "^2.30.1",
        "pinia": "^3.0.1",
        "spark-md5": "^3.0.2",
        "three": "^0.177.0",
        "three-fbx-loader": "^1.0.3",
        "three-orbitcontrols": "^2.110.3",
        "vue": "^3.4.21",
        "vue-router": "^4.3.0"
      },
@@ -1577,6 +1580,11 @@
      "resolved": "https://registry.npmmirror.com/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz",
      "integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw=="
    },
    "node_modules/pako": {
      "version": "1.0.11",
      "resolved": "https://registry.npmmirror.com/pako/-/pako-1.0.11.tgz",
      "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="
    },
    "node_modules/parse-node-version": {
      "version": "1.0.1",
      "resolved": "https://registry.npmmirror.com/parse-node-version/-/parse-node-version-1.0.1.tgz",
@@ -1805,6 +1813,34 @@
      },
      "funding": {
        "url": "https://github.com/sponsors/mesqueeb"
      }
    },
    "node_modules/three": {
      "version": "0.177.0",
      "resolved": "https://registry.npmmirror.com/three/-/three-0.177.0.tgz",
      "integrity": "sha512-EiXv5/qWAaGI+Vz2A+JfavwYCMdGjxVsrn3oBwllUoqYeaBO75J63ZfyaQKoiLrqNHoTlUc6PFgMXnS0kI45zg=="
    },
    "node_modules/three-fbx-loader": {
      "version": "1.0.3",
      "resolved": "https://registry.npmmirror.com/three-fbx-loader/-/three-fbx-loader-1.0.3.tgz",
      "integrity": "sha512-CQZ0IkwqX+ZbIca225mfKCq1feH8XJOf2zqMcC2bugvq20S7alHGsG9QEELbyN9zSL0EvkBXADVDO2lupF0E+A==",
      "dependencies": {
        "pako": "^1.0.6",
        "three": "^0.89.0"
      }
    },
    "node_modules/three-fbx-loader/node_modules/three": {
      "version": "0.89.0",
      "resolved": "https://registry.npmmirror.com/three/-/three-0.89.0.tgz",
      "integrity": "sha512-Evv3JolLQtag/lCWu1o3cgz2E5UYCSZHbh9lfBjeLz7nlK8DMmASID680Odl++ZPYk63rrZjPAMq88IrMP3GCA=="
    },
    "node_modules/three-orbitcontrols": {
      "version": "2.110.3",
      "resolved": "https://registry.npmmirror.com/three-orbitcontrols/-/three-orbitcontrols-2.110.3.tgz",
      "integrity": "sha512-BNNbksJwbN3/MmT0X/gjz5ZCchm7bjk26SUdtJYRxfEYjDfkb/0PeUTHE/KuyJ5vb/owK3mojyy3vcqDx99sRA==",
      "deprecated": "three-js exposes real modules now via three/examples/jsm/...\nfor example to import Orbit, do\nimport { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'\n",
      "peerDependencies": {
        "three": ">= 0.110.0"
      }
    },
    "node_modules/tslib": {
@@ -2877,6 +2913,11 @@
      "resolved": "https://registry.npmmirror.com/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz",
      "integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw=="
    },
    "pako": {
      "version": "1.0.11",
      "resolved": "https://registry.npmmirror.com/pako/-/pako-1.0.11.tgz",
      "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="
    },
    "parse-node-version": {
      "version": "1.0.1",
      "resolved": "https://registry.npmmirror.com/parse-node-version/-/parse-node-version-1.0.1.tgz",
@@ -3040,6 +3081,33 @@
        }
      }
    },
    "three": {
      "version": "0.177.0",
      "resolved": "https://registry.npmmirror.com/three/-/three-0.177.0.tgz",
      "integrity": "sha512-EiXv5/qWAaGI+Vz2A+JfavwYCMdGjxVsrn3oBwllUoqYeaBO75J63ZfyaQKoiLrqNHoTlUc6PFgMXnS0kI45zg=="
    },
    "three-fbx-loader": {
      "version": "1.0.3",
      "resolved": "https://registry.npmmirror.com/three-fbx-loader/-/three-fbx-loader-1.0.3.tgz",
      "integrity": "sha512-CQZ0IkwqX+ZbIca225mfKCq1feH8XJOf2zqMcC2bugvq20S7alHGsG9QEELbyN9zSL0EvkBXADVDO2lupF0E+A==",
      "requires": {
        "pako": "^1.0.6",
        "three": "^0.89.0"
      },
      "dependencies": {
        "three": {
          "version": "0.89.0",
          "resolved": "https://registry.npmmirror.com/three/-/three-0.89.0.tgz",
          "integrity": "sha512-Evv3JolLQtag/lCWu1o3cgz2E5UYCSZHbh9lfBjeLz7nlK8DMmASID680Odl++ZPYk63rrZjPAMq88IrMP3GCA=="
        }
      }
    },
    "three-orbitcontrols": {
      "version": "2.110.3",
      "resolved": "https://registry.npmmirror.com/three-orbitcontrols/-/three-orbitcontrols-2.110.3.tgz",
      "integrity": "sha512-BNNbksJwbN3/MmT0X/gjz5ZCchm7bjk26SUdtJYRxfEYjDfkb/0PeUTHE/KuyJ5vb/owK3mojyy3vcqDx99sRA==",
      "requires": {}
    },
    "tslib": {
      "version": "2.8.1",
      "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz",
package.json
@@ -16,6 +16,9 @@
    "moment": "^2.30.1",
    "pinia": "^3.0.1",
    "spark-md5": "^3.0.2",
    "three": "^0.177.0",
    "three-fbx-loader": "^1.0.3",
    "three-orbitcontrols": "^2.110.3",
    "vue": "^3.4.21",
    "vue-router": "^4.3.0"
  },
src/components/previewModelDialog.vue
New file
@@ -0,0 +1,134 @@
<template>
  <el-dialog
    v-model="dialogVisible"
    @open="openDialog"
    destroy-on-close
    class="prviewModel"
  >
    <div  v-loading="loading">
      <div
        id="container"
        class="viewer-container"
        v-if="selectData.md5"
      ></div>
      <el-empty v-else description="暂无数据" />
    </div>
  </el-dialog>
</template>
<script setup lang="ts">
import * as THREE from "three";
import { FBXLoader } from "three/examples/jsm/loaders/FBXLoader";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import {  ref, defineExpose, inject } from "vue";
const config: any = inject("config");
const dialogVisible = ref<boolean>(false);
const selectData = ref<any>(null);
const loading = ref<boolean>(false);
const openDialog = () => {
  loading.value = true;
  if (selectData.value.md5) {
    initThree();
  } else {
    loading.value = false
  }
};
const initThree = () => {
  const container = document.getElementById("container");
  const scene = new THREE.Scene();
  const camera = new THREE.PerspectiveCamera(
    75,
    container.clientWidth / container.clientHeight,
    0.1,
    10000 // 增大远平面距离
  );
  const renderer = new THREE.WebGLRenderer({ antialias: true });
  renderer.setSize(container.clientWidth, container.clientHeight);
  container.appendChild(renderer.domElement);
  // 添加光源
  const light = new THREE.DirectionalLight(0xffffff, 2);
  light.position.set(5, 10, 7.5);
  scene.add(light);
  // 加载模型
  const loader = new FBXLoader();
  const token = localStorage.getItem(config.tokenKey);
  loader.load(
    config.requestCtx +
      "/file/FileDownload/Download?md5=" +
      selectData.value.md5 +
      "&token=" +
      token,
    (object) => {
      scene.add(object);
      // 创建控件
      const controls = new OrbitControls(camera, renderer.domElement);
      controls.enableDamping = true;
      controls.dampingFactor = 0.25;
      controls.screenSpacePanning = false;
      // 1. 计算模型尺寸和中心点
      const box = new THREE.Box3().setFromObject(object);
      const center = box.getCenter(new THREE.Vector3());
      const size = box.getSize(new THREE.Vector3());
      // 2. 计算合适的相机距离
      const maxDim = Math.max(size.x, size.y, size.z);
      const fov = camera.fov * (Math.PI / 180);
      let cameraDistance = Math.abs(maxDim / (2 * Math.tan(fov / 2)));
      // 3. 设置相机位置和方向
      camera.position.copy(center);
      camera.position.z += cameraDistance * 1.5; // 增加50%的额外空间
      camera.lookAt(center);
      // 4. 更新控件目标点
      controls.target.copy(center);
      controls.update();
      // 5. 调整相机剪裁平面
      const minDistance = cameraDistance * 0.1;
      const maxDistance = cameraDistance * 10;
      camera.near = minDistance > 0.1 ? minDistance : 0.1;
      camera.far = maxDistance < 10000 ? maxDistance : 10000;
      camera.updateProjectionMatrix();
      function animate() {
        requestAnimationFrame(animate);
        controls.update();
        renderer.render(scene, camera);
      }
      animate();
      loading.value = false
    },
    undefined,
    (error) => {
      console.error("加载模型失败:", error);
      loading.value = false
    },
  );
};
const handleDialogVisible = (value: boolean, data: any) => {
  dialogVisible.value = value;
  selectData.value = data;
  console.log("select", selectData.value);
};
defineExpose({ handleDialogVisible });
</script>
<style lang="less">
.prviewModel {
  width: 60vw;
  min-height: 580px;
  .viewer-container {
    width: 100%;
    height: 60vh;
    .el-empty {
      margin-top: 100px !important;
    }
  }
}
</style>
src/components/showModel.vue
New file
@@ -0,0 +1,109 @@
<template>
  <div class="showModel" v-loading="loading">
    <div :id="'container' + props.md5 + props.index" class="viewer-container" v-if="props.md5"></div>
    <el-empty v-else description="暂无数据" image-size="80" />
  </div>
</template>
<script setup lang="ts">
import { ref, onMounted, defineProps,inject } from "vue";
import * as THREE from "three";
import { FBXLoader } from "three/examples/jsm/loaders/FBXLoader";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
const props = defineProps<{md5:string,index:number}>();
const config: any = inject("config");
const loading = ref<boolean>(false);
onMounted(() => {
  loading.value = true;
  if(props.md5) {
     initThree();
  } else {
    loading.value = false;
  }
});
const initThree = () => {
  const container = document.getElementById("container" + props.md5 + props.index);
  const scene = new THREE.Scene();
  const camera = new THREE.PerspectiveCamera(
    75,
    container.clientWidth / container.clientHeight,
    0.1,
    10000 // 增大远平面距离
  );
  const renderer = new THREE.WebGLRenderer({ antialias: true });
  renderer.setSize(container.clientWidth, container.clientHeight);
  container.appendChild(renderer.domElement);
  // 添加光源
  const light = new THREE.DirectionalLight(0xffffff, 2);
  light.position.set(5, 10, 7.5);
  scene.add(light);
  // 加载模型
  const loader = new FBXLoader();
  const token = localStorage.getItem(config.tokenKey);
  loader.load(
    config.requestCtx +
      "/file/FileDownload/Download?md5=" +
      props.md5 +
      "&token=" +
      token,
    (object) => {
      scene.add(object);
      // 创建控件
      const controls = new OrbitControls(camera, renderer.domElement);
      controls.enableDamping = true;
      controls.dampingFactor = 0.25;
      controls.screenSpacePanning = false;
      // 1. 计算模型尺寸和中心点
      const box = new THREE.Box3().setFromObject(object);
      const center = box.getCenter(new THREE.Vector3());
      const size = box.getSize(new THREE.Vector3());
      // 2. 计算合适的相机距离
      const maxDim = Math.max(size.x, size.y, size.z);
      const fov = camera.fov * (Math.PI / 180);
      let cameraDistance = Math.abs(maxDim / (2 * Math.tan(fov / 2)));
      // 3. 设置相机位置和方向
      camera.position.copy(center);
      camera.position.z += cameraDistance * 1.5; // 增加50%的额外空间
      camera.lookAt(center);
      // 4. 更新控件目标点
      controls.target.copy(center);
      controls.update();
      // 5. 调整相机剪裁平面
      const minDistance = cameraDistance * 0.1;
      const maxDistance = cameraDistance * 10;
      camera.near = minDistance > 0.1 ? minDistance : 0.1;
      camera.far = maxDistance < 10000 ? maxDistance : 10000;
      camera.updateProjectionMatrix();
      function animate() {
        requestAnimationFrame(animate);
        controls.update();
        renderer.render(scene, camera);
      }
      animate();
      loading.value = false;
    },
    undefined,
    (error) => {
      console.error("加载模型失败:", error);
      loading.value = false;
    }
  );
};
</script>
<style lang="less" scoped>
.showModel,
.viewer-container {
  width: 100%;
  height: 100%;
}
</style>
src/views/model/children/landerModel.vue
@@ -92,16 +92,8 @@
        />
      </div>
    </div>
    <el-dialog v-model="dialogTableVisible" title="预览">
      <div>
        <iframe
          style="width: 100%; height: 500px"
          src="../static/modelView/index.html?md5=62d4eadc420b7403fce2be993baa095d&name=飞行棋&domain=https://www.jlstp.cn&target=iframe"
          frameborder="0"
        ></iframe>
      </div>
    </el-dialog>
    <!-- 预览 -->
  <previewModule ref="previewModelRef"></previewModule>
    <el-dialog v-model="dialogFormVisible" title="新建模型">
      <el-form :rules="rules" :model="form" ref="formRef">
        <el-form-item
@@ -122,7 +114,11 @@
          />
        </el-form-item>
        <el-form-item label="模型文件" :label-width="formLabelWidth">
          <el-upload :before-upload="beforeUpload" :limit="1" v-if="!form.ModelFile">
          <el-upload
            :before-upload="beforeUpload"
            :limit="1"
            v-if="!form.ModelFile"
          >
            <template #trigger>
              <el-button type="primary">上传模型</el-button>
            </template>
@@ -156,6 +152,7 @@
import { Plus } from "@element-plus/icons-vue";
import { curStoreInfo } from "@/store/index";
import SparkMD5 from "spark-md5";
import previewModule from "@/components/previewModelDialog.vue";
import {
  ComponentSize,
  ElMessage,
@@ -192,7 +189,7 @@
  JointData: "",
  IsSimulation: false,
  ModelRemarks: "",
  ModelFile: null
  ModelFile: null,
});
const progress = ref(0);
@@ -348,8 +345,18 @@
  getTableData();
};
//预览操作
const handlePreview = (row) => {
  dialogTableVisible.value = true;
const previewModelRef = ref<any>();
const showModelData = ref<any>(null);
const handlePreview = (row: any) => {
  let md5: any = null;
  try {
    const fileData = row.fieldList.find((item: any) => item.FileList.length);
    md5 = fileData.FileList[0].File.Md5;
  } catch (error) {}
  previewModelRef.value.handleDialogVisible(true, {
    name: row.name,
    md5,
  });
};
//上移操作
const handleMoveUp = (row) => {};
src/views/simulation/testSimulation/index.vue
@@ -46,11 +46,12 @@
            >
              <div class="model-body-box">
                <div class="model-img">
                  <iframe
                  <!-- <iframe
                    style="width: 100%; height: 100%"
                    src="./static/modelView/index.html?md5=62d4eadc420b7403fce2be993baa095d&name=飞行棋&domain=https://www.jlstp.cn&target=iframe"
                    frameborder="0"
                  ></iframe>
                  ></iframe> -->
                  <showModel :md5="item.md5" :index="index"></showModel>
                </div>
                <div class="model-info">
                  <h1 class="model-title" :title="item.name">
@@ -94,6 +95,7 @@
<script setup lang="ts">
import { ref, onMounted, watch, inject } from "vue";
import { useRouter, useRoute } from "vue-router";
import showModel from "../../../components/showModel.vue";
const router = useRouter();
const route = useRoute();
import { curStoreInfo } from "@/store/index";
@@ -205,12 +207,21 @@
    },
    {
      ModelName: [],
      ModelFile: [],
      JointData: [],
      IsSimulation: [],
      ModelRemarks: [],
      ChildrenCount: [],
    }
  );
  for (let index = 0; index < treeData.datas.length; index++) {
    const item = treeData.datas[index];
    item.md5 = null;
    try {
      const fileData = item.fieldList.find((citem: any) => citem.FileList.length);
      item.md5 = fileData.FileList[0].File.Md5;
    } catch (error) {}
  }
  console.log(treeData, "getModelList");
  modelDataList.value = treeData.datas;
  listLoading.value = false;
@@ -262,7 +273,6 @@
const gotoReport = (item) => {
  router.push({
    name: "simulation-testReport",
  });
};
//打开仿真