222 lines
6.4 KiB
Vue
222 lines
6.4 KiB
Vue
<template>
|
||
<Dialog
|
||
ref="baseDialog"
|
||
title="模型预览器"
|
||
class="imagePop"
|
||
left="180px"
|
||
top="100px"
|
||
:closeCallback="closeCallBack"
|
||
>
|
||
<template #content>
|
||
<div class="content">
|
||
<span class="custom-divider"></span>
|
||
<div class="imageCon" ref="threeCanvas">
|
||
<!-- <img class="image" :src="rowData.thumbnail" alt="" /> -->
|
||
</div>
|
||
<div class="inputCon">
|
||
<span class="label">模型名称</span>
|
||
<input class="input" maxlength="40" type="text" v-model="newData.name" />
|
||
</div>
|
||
</div>
|
||
</template>
|
||
<template #footer>
|
||
<div style="position: absolute; left: -400px; display: flex">
|
||
<button @click="setImage">设置预览图</button>
|
||
</div>
|
||
<button @click="save">保存</button>
|
||
<button @click="close">关闭</button>
|
||
</template>
|
||
</Dialog>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref, reactive } from 'vue'
|
||
import { inject } from 'vue'
|
||
import Dialog from '@/components/dialog/baseDialog.vue'
|
||
import * as THREE from 'three'
|
||
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
|
||
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
|
||
import { debounce } from '@/utils'
|
||
import { ModelApi } from '@/api/model/index'
|
||
import { ElMessage } from 'element-plus'
|
||
|
||
const baseDialog: any = ref(null)
|
||
const eventBus: any = inject('bus')
|
||
const viewPointHeight: any = ref(1.8)
|
||
var rowData: any = reactive([])
|
||
var imageData: any = ref(null)
|
||
var newData: any = reactive({
|
||
name: ''
|
||
})
|
||
eventBus.on('imagePopDialog', (data) => {
|
||
rowData = data
|
||
rowData.name = data.modelName
|
||
baseDialog.value?.open()
|
||
setTimeout(() => {
|
||
initThreeJS()
|
||
loadModel()
|
||
animate()
|
||
})
|
||
})
|
||
//----------------模型预览---------------
|
||
const threeCanvas = ref(null)
|
||
let scene, camera, renderer, model, controls
|
||
|
||
const initThreeJS = () => {
|
||
// 创建场景
|
||
scene = new THREE.Scene()
|
||
scene.background = new THREE.Color(0xbfd1e5)
|
||
// scene.fog = new THREE.Fog(0xbfd1e5, 200, 1000)
|
||
|
||
// 创建相机
|
||
camera = new THREE.PerspectiveCamera(
|
||
75,
|
||
threeCanvas.value.clientWidth / threeCanvas.value.clientHeight,
|
||
0.1,
|
||
1000
|
||
)
|
||
camera.position.set(0, 1, 5)
|
||
camera.lookAt(0, 0, 0)
|
||
|
||
// 创建渲染器并添加到DOM中
|
||
renderer = new THREE.WebGLRenderer({
|
||
preserveDrawingBuffer: true,
|
||
antialias: true
|
||
})
|
||
renderer.setSize(threeCanvas.value.clientWidth, threeCanvas.value.clientHeight)
|
||
renderer.setPixelRatio(window.devicePixelRatio)
|
||
threeCanvas.value.appendChild(renderer.domElement)
|
||
|
||
// 控制器
|
||
controls = new OrbitControls(camera, renderer.domElement)
|
||
controls.enableDamping = true // 启用阻尼,增加平滑度
|
||
controls.dampingFactor = 0.05 // 阻尼系数,默认为0.25
|
||
controls.screenSpacePanning = false // 平移时是否在屏幕空间中移动,默认false,即在世界空间中移动
|
||
controls.minDistance = 1 // 最小距离,默认无限制
|
||
controls.maxDistance = 500 // 最大距离,默认无限制
|
||
controls.enableZoom = true // 是否可以缩放,默认true
|
||
controls.zoomSpeed = 1.0 // 缩放速度,默认1.0
|
||
controls.rotateSpeed = 1.0 // 旋转速度,默认1.0
|
||
controls.addEventListener('change', render) // 监听控制器变化进行重渲染
|
||
|
||
// 添加环境光和点光源
|
||
const ambientLight = new THREE.AmbientLight(0x404040, 1) // 环境光
|
||
scene.add(ambientLight)
|
||
const directionalLight = new THREE.DirectionalLight(0xffffff, 1)
|
||
scene.add(directionalLight)
|
||
const light = new THREE.HemisphereLight(0xffffbb, 0x080820, 1)
|
||
scene.add(light)
|
||
const pointLight = new THREE.PointLight(0xffffff, 0.8) // 点光源
|
||
pointLight.position.set(10, 10, 10)
|
||
pointLight.lookAt(0, 0, 0)
|
||
scene.add(pointLight)
|
||
}
|
||
const loadModel = () => {
|
||
const loader = new GLTFLoader()
|
||
loader.load(
|
||
'http://127.0.0.1:8848' + rowData.data, // 模型路径
|
||
function (gltf) {
|
||
// onLoad回调函数
|
||
model = gltf.scene
|
||
model.position.set(0, 0, 0)
|
||
scene.add(model) // 将模型添加到场景中
|
||
},
|
||
undefined, // onProgress回调函数,可选参数,用于处理加载进度等,这里不使用所以设置为undefined或提供具体实现函数。
|
||
function (error) {
|
||
// onError回调函数,处理加载错误等。
|
||
console.error('An error happened while loading the model', error)
|
||
}
|
||
)
|
||
}
|
||
const render = () => {
|
||
renderer.render(scene, camera) // 重渲染当前场景和相机状态,通常用于控制器监听事件中调用
|
||
}
|
||
|
||
const animate = () => {
|
||
requestAnimationFrame(animate) // 循环调用animate函数实现动画效果。
|
||
controls.update() // 更新控制器状态
|
||
renderer.render(scene, camera) // 渲染场景和相机。
|
||
}
|
||
|
||
const clangeViewPointHeight = () => {}
|
||
const viewPointHeightInput = () => {
|
||
let dom: any = document.getElementById('viewPointHeight')
|
||
if (viewPointHeight.value < dom.min * 1) {
|
||
viewPointHeight.value = dom.min * 1
|
||
} else if (viewPointHeight.value > dom.max * 1) {
|
||
viewPointHeight.value = dom.max * 1
|
||
}
|
||
}
|
||
const closeCallBack = (e) => {
|
||
renderer.domElement.remove() // 从DOM中移除渲染器。
|
||
imageData.value = null
|
||
rowData = []
|
||
newData.name = ''
|
||
//打开系统设置弹框
|
||
eventBus.emit('settingPop', true)
|
||
}
|
||
|
||
var posterLoading: any = ref(false)
|
||
const setImage = (e) => {
|
||
renderer.render(scene, camera) // 确保场景已渲染
|
||
const canvas = renderer.domElement
|
||
|
||
canvas.toBlob((blob) => {
|
||
imageData.value = blob
|
||
ElMessage.warning('点击保存是会应用当前图片')
|
||
}, 'image/png')
|
||
}
|
||
const close = (e) => {
|
||
baseDialog.value?.close()
|
||
}
|
||
const save = (e) => {
|
||
const formData = new FormData()
|
||
formData.append('modelId', rowData.id)
|
||
newData.name && formData.append('modelName', newData.name)
|
||
imageData.value && formData.append('file', imageData.value)
|
||
ModelApi.updatePoster(formData).then((res) => {
|
||
if (res.code == 0 || res.code == 200) {
|
||
ElMessage.success('设置成功')
|
||
}
|
||
})
|
||
baseDialog.value?.close()
|
||
}
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
.imageCon {
|
||
width: 400px;
|
||
height: 400px;
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
float: left;
|
||
}
|
||
.image {
|
||
width: 100%;
|
||
height: 100%;
|
||
object-fit: contain;
|
||
}
|
||
.inputCon {
|
||
width: 180px;
|
||
float: left;
|
||
margin-left: 10px;
|
||
margin-top: 10px;
|
||
}
|
||
.inputCon span {
|
||
display: block;
|
||
width: 60px;
|
||
height: 32px;
|
||
line-height: 32px;
|
||
float: left;
|
||
}
|
||
.inputCon .input {
|
||
float: left;
|
||
width: 120px;
|
||
}
|
||
.content {
|
||
width: 600px;
|
||
height: 400px;
|
||
}
|
||
</style>
|