Files
tunnel-cloud-web/src/components/content/tunnelScene/TunnelSceneSimulate.vue
2025-01-02 23:33:37 +08:00

466 lines
12 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div id="scene">
<div id="cvs" ref="content"></div>
<!-- 这里的预览模式需要做成不能修改的模式 -->
<edit-dialog
ref="edit"
@addEquipment="handleAddEqu"
@removeEquipment="handleRemoveEqu"
@cancel="handleCancel"
:hasDev="hasDevice"
:pointNum="pointNum"
:tunnelId="params.tunnelId"
:position="targetP?.name"
:hasEquipment="hasDevice"
:pointGap="pointGap"
:form="params.form"
/>
<pre-dialog
ref="info"
:pointNum="50"
:pointGap="50"
:equipmentType="equipmentType"
:equipmentName="equipmentName"
:equipmentValue="50"
:devRealtimeDetail="devRealtimeDetail"
/>
</div>
</template>
<script setup>
import * as three from "three";
import ThreeDScene from "./sceneClass/demo.js";
import DevInfo from "./displayInfoComp/DevInfo.vue";
import EditDialog from "./editEquComp/editDialog.vue";
import PreDialog from "./preEquComp/preDialog.vue";
// 导入模模型加载器
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
// import { DRACOLoader } from "three/examples/jsm/loaders/dracoloader";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import * as TWEEN from "three/examples/jsm/libs/tween.module";
import {
CSS2DRenderer,
CSS2DObject,
} from "three/addons/renderers/CSS2DRenderer.js";
// 引入CSS2渲染器CSS2DRenderer和CSS2模型对象CSS2DObject
import { OBJLoader } from "three/examples/jsm/loaders/OBJLoader";
import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader";
import {
onMounted,
reactive,
ref,
watch,
defineProps,
defineExpose,
} from "vue";
import { ElMessage } from "element-plus";
import { getScreenSimulateTunnel } from "@/api/tunnelManage";
// 获取html标签跟随组件dom
const content = ref(null);
const info = ref(null);
const edit = ref(null);
let modelList = ref(null);
let demo; //定义demo对象
const loader = new OBJLoader();
let hdrLoader = new RGBELoader();
let backColorSet = three.sRGBEncoding;
const params = defineProps([
"isedit",
"tunnelId",
"tunnelLength",
"form",
"deviceData",
"devRealtimeData",
"simulateData",
"tunnelAlias",
]);
// hover到设备回显参数
let equipmentName = ref("");
let equipmentType = ref("");
let devRealtimeDetail = ref({
equipmentType: "",
value: "",
unit: "",
});
watch(
() => params.tunnelLength,
(now) => {
params.tunnelLength = now;
pointGap = now / 20;
},
{ deep: true }
);
let pointGap = reactive(params.tunnelLength);
onMounted(handleMounted);
// 挂载后回调
async function handleMounted() {
const doms = [info.value.$el, info.value.$el];
demo = new ThreeDScene(three, content.value);
//看是不是预览模式,然后继续相关的操作(会在demo中的初始化中进行)
// "../../../../public/tunnelModel/chanel-have-wall-now-use.gltf"
demo.isedit = false;
const loaded = await demo.loadModel(
GLTFLoader,
"/tunnelModel/chanel-have-wall-now-use.gltf"
);
demo.addOrbitControls(OrbitControls, true);
demo.addTween(TWEEN);
demo.addCSS3Renderer(CSS2DRenderer, CSS2DObject, doms);
demo.setDistance(10);
lClickCallback(demo); //绑定左键回调
rClickCallback(demo); //绑定右键回调
//加载HDR背景图片
demo.loadBackground(hdrLoader, backColorSet);
// 初始化设备模型
try {
const map = new Map();
map.set("equ_fan", await loadModel("/devicesModel/model2.obj"));
map.set("equ_sensors", await loadModel("/devicesModel/sensors.obj"));
demo.initDevicesModel(map);
modelList.value = [
{
typeKey: "frequency",
position: "point_001_tr",
threshold: "",
equipmentId: 3048,
equipmentName: "温度传感器",
equipmentType: "sensor",
data: 100, //实时数据
unit: "℃", //单位
},
{
typeKey: "temperature",
position: "point_020_tr",
threshold: "",
equipmentId: 10,
equipmentName: "1度传感器",
equipmentType: "sensor",
data: 10, //实时参数
unit: "%", //单位
},
];
// 初始化仿真參數
const { data } = await getScreenSimulateTunnel(params.tunnelId);
demo.enableLeftBtn = false; //关闭左键
demo.pointsVisible(false); //隐藏附着点
TunnelSceneSimulateRender(data);
// 初始化标牌信息
renderRoadPlane({ nickname: params.tunnelAlias, length: params.tunnelLength });
} catch (err) {}
}
// 从新渲染数据
async function rerender() {
try {
// 初始化仿真參數
const { data } = await getScreenSimulateTunnel(params.tunnelId);
console.log(data);
const modeData = randomPosition([
...data.frequencyChangerList,
...data.windPressureSensorList,
...data.sensorList,
]).map((item) => ({
...item,
equipmentType: item.equipmentType.startsWith("frequency")
? "frequency"
: "sensor",
}));
demo.editTunnelInit(modeData);
demo.SignsInf(params.form.tunnelAlias, String(params.tunnelLength));
} catch (error) {
ElMessage({
message: "获取仿真数据异常!",
type: "warning",
});
}
}
// 随机生成1-20的随机数
function randomNum(max = 20) {
const nums = new Set();
while (nums.size < max) {
nums.add(Math.floor(Math.random() * max + 1).toString());
}
return [...nums];
}
//随机定位
function randomPosition(simulateData) {
const mid = Math.floor(simulateData.length / 2);
const lrIndex = Math.floor(Math.random() * 2);
const lr_position = ["tl", "tr"];
const leftPosition = randomNum();
const rightPosition = randomNum();
const leftData = simulateData.slice(0, mid).map((item, index) => ({
...item,
position: `point_0${leftPosition[index].padStart(2, 0)}_${
lr_position[lrIndex]
}`,
}));
const rightData = simulateData
.slice(mid, simulateData.length)
.map((item, index) => ({
...item,
position: `point_0${rightPosition[index].padStart(2, 0)}_${
lr_position[1 - lrIndex]
}`,
}));
return [...leftData, ...rightData];
}
// 每个模型加载回调
function loadModel(path) {
return new Promise((resolve, reject) => {
loader.load(
path,
(obj) => {
resolve(obj);
},
(xhr) => {},
(err) => {
reject(err);
}
);
});
}
let hasDevice = ref(true);
let devInfo = reactive({
meshId: null,
name: "无",
state: "无",
position: "无",
});
// 对象建构赋值功能
function editDevInfo(
value = {
meshId: null,
name: "无",
state: "无",
position: "无",
}
) {
devInfo.meshId = value.meshId;
devInfo.name = value.name;
devInfo.state = value.state;
devInfo.position = value.position;
}
//左键/双击左键回调函数
function lClickCallback(demo) {
// demo动态添加函数为操作组件内部
function displayDevInfo(targetPoint = null) {
hasDevice.value = targetPoint.hasDevice;
if (!targetPoint.info) {
editDevInfo();
return;
}
editDevInfo(targetPoint.info);
}
// 传给内部使用
demo.addFunction("displayDevInfo", displayDevInfo);
}
let pointNum = ref(0);
let targetP = ref({});
// 右键点击附着点后调函数
function rClickCallback(demo) {
function editDev(targetPoint = null) {
hasDevice.value = targetPoint.hasDevice;
targetP.value = targetPoint;
pointNum.value = Number(
targetPoint.name.substring(
targetPoint.name.indexOf("_") + 1,
targetPoint.name.lastIndexOf("_")
) - 1
);
if (!targetPoint.info) return;
hoverDevEquipmentCallback(targetPoint.info);
editDevInfo(targetPoint.info);
}
demo.addFunction("editDev", editDev);
}
// 场景添加设备
function handleAddEqu(formInfo) {
demo.addEquipment(targetP.value, formInfo);
// //将墙壁设置为可以穿透点击
for (let line = 1; line <= 20; line++) {
if (line < 10) {
let wall = "wall_" + "0" + line;
this.scene.getObjectByName(wall).layers.set(0);
} else if (line >= 10) {
let wall = "wall_" + line;
this.scene.getObjectByName(wall).layers.set(0);
}
}
}
const centerDialogVisible = ref(false);
// 删除设备
function handleRemoveEqu() {
if (!targetP.value.hasDevice) {
ElMessage({
message: "该点位不存在设备!",
type: "warning",
});
return;
}
centerDialogVisible.value = true;
}
// 处理取消关闭编辑框事件
function handleCancel() {
if (!demo) return;
// 关闭标签
demo.isControlOrbit(true);
demo._resetState();
demo.clearTagsObj();
}
//现在首先有二种方案是写在TunnelScene.vue中还是demo.js中呢
//我认为可能看数据在哪获取到时候看在哪里导入首先我们放在TunnelScene里面吧
//因为没获取到接口,我们先写死!!!
//需要参考接口的数据结构!!!,主要是传入函数的参数应该是什么结构???
const ThreeConfig = {
code: 0,
data: {
tunnelThreeConfig: [
{
equipmentId: "fan_01", //传感器id
equipmentName: "01", //设备名称
pointName: "point_005_tl", //附着点名称(定位)
equipmentType: "fan", //设备类型(类型可根据后端
equipmentValue: 23, //设备存的值
},
{
equipmentId: "sensors_01", //传感器id
equipmentName: "01", //设备名称
pointName: "point_009_bl", //附着点名称(定位)
equipmentType: "sensors", //设备类型(类型可根据后端
equipmentValue: 67, //设备存的值
},
],
},
msg: "dda",
};
//=============隧模型道数据仿真====================
/**
* @description: 隧道中的设备重新模拟渲染
* @param {Object} data 渲染数据格式 [{equipmentId,equipmentName,pointName,equipmentType,equipmentValue}]
*/
function TunnelSceneSimulateRender(data) {
console.log('TunnelSceneSimulateRender',data)
if (!demo) return;
const modeData = randomPosition([
...data.frequencyChangerList,
...data.windPressureSensorList,
...data.sensorList,
]).map((item) => ({
...item,
equipmentType: item.equipmentType.startsWith("frequency")
? "frequency"
: "sensor",
}));
demo.editTunnelInit(modeData); //渲染设备数据
}
/**
* @description: 鼠标悬浮在设备上时回调函数
*/
function hoverDevEquipmentCallback(targetPointInfo) {
console.log(targetPointInfo); //当前附着点信息
const {
equipmentId,
equipmentName: eName,
equipmentType: eType,
typeKey,
unit: u,
} = targetPointInfo;
//请求到的实时数据进行渲染
//业务代码.... 获取设备实时数据(data) const value = ....
equipmentType.value = eType;
equipmentName.value = eName;
devRealtimeDetail.value.equipmentType =typeKey;
devRealtimeDetail.value.unit = u;
devRealtimeDetail.value.value = 1000; //1000替换为value
}
/**
* @description: 渲染隧道入口提示板信息
* @param {Object} data 隧道信息 {nickname,length}
*/
function renderRoadPlane(data) {
if (!demo) return;
const { nickname, length } = data; //隧道名称,长度
demo.SignsInf(nickname, length + "");
}
defineExpose({
rerender,TunnelSceneSimulateRender
});
</script>
<style lang="scss" scoped>
#scene {
position: relative;
height: 100%;
width: 100%;
#cvs {
height: 100%;
}
}
#remove-title {
height: 42px;
font-size: 32px;
font-family: MicrosoftYaHei, MicrosoftYaHei;
font-weight: bold;
color: #08b7b8;
line-height: 42px;
letter-spacing: 3px;
text-align: center;
margin: 65px 0px 70px 0px;
}
.btn {
display: flex;
justify-content: center;
gap: 80px;
:nth-child(1) {
width: 190px;
height: 60px;
border-radius: 11px;
border: 2px solid #0f82af;
font-size: 28px;
font-family: MicrosoftYaHei;
color: #08b7b8;
line-height: 37px;
background: transparent;
}
:nth-child(2) {
width: 190px;
height: 60px;
background: #08b7b8;
border-radius: 11px;
font-size: 28px;
font-family: MicrosoftYaHei, MicrosoftYaHei;
font-weight: bold;
color: #ffffff;
line-height: 37px;
}
}
</style>