From 8c96a1d16d1d95101938670b287e39130fd5e086 Mon Sep 17 00:00:00 2001 From: 闫增涛 <1829501689@qq.com> Date: 星期四, 12 六月 2025 17:12:51 +0800 Subject: [PATCH] 模型预览 --- src/components/previewModelDialog.vue | 134 +++++++++++++++++++ package-lock.json | 68 +++++++++ package.json | 3 src/components/showModel.vue | 109 +++++++++++++++ src/views/model/children/landerModel.vue | 35 +++-- src/views/simulation/testSimulation/index.vue | 16 + 6 files changed, 348 insertions(+), 17 deletions(-) diff --git a/package-lock.json b/package-lock.json index 902d3c7..e394111 100644 --- a/package-lock.json +++ b/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", diff --git a/package.json b/package.json index 2cd5334..8c5de90 100644 --- a/package.json +++ b/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" }, diff --git a/src/components/previewModelDialog.vue b/src/components/previewModelDialog.vue new file mode 100644 index 0000000..34792c1 --- /dev/null +++ b/src/components/previewModelDialog.vue @@ -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> diff --git a/src/components/showModel.vue b/src/components/showModel.vue new file mode 100644 index 0000000..0357ee8 --- /dev/null +++ b/src/components/showModel.vue @@ -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> diff --git a/src/views/model/children/landerModel.vue b/src/views/model/children/landerModel.vue index 5fc6331..136b11c 100644 --- a/src/views/model/children/landerModel.vue +++ b/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) => {}; diff --git a/src/views/simulation/testSimulation/index.vue b/src/views/simulation/testSimulation/index.vue index dae9cec..2303f82 100644 --- a/src/views/simulation/testSimulation/index.vue +++ b/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", - }); }; //鎵撳紑浠跨湡 -- Gitblit v1.9.1