可视化3D插件开发
接下来我们为官方插件开发一个可以拖拽到画布上的3D场景组件,
- 可以通过右侧属性面板3d场景中设备标签的文字大小,文字颜色和背景颜色。
- 通过数据面板设置设备标签中的展示文字
- 通过绑定设备在设备标签上显示接口数据 最终效果如下图: 如上图所示,插件开发完成后,我们只需两步就可完成3d场景的搭建,和数据接入,是不是非常方便,接下来我们进入开发过程
开发文档
第一步:创建组件所需的文件
官方插件所在的目录是src/plugins/tp-plugins文件夹,我们在这个文件夹里创建threejs-demo的目录,并在threejs-demo目录创建以下4个文件: index.ts、Main.vue、Data.vue、Attribute.vue,并为在左侧列表展示图标添加一个icon.svg 其中
- index.ts 为数据导出出口文件
- Main.vue 插件的主要组件
- Data.vue 数据绑定面板组件
- Attribute.vue 属性调整面板组件
- icon.svg 左侧列表的图标
第二步:准备3d引擎文件:engine.js
准备好你开发好的3d场景文件engine.js,该文件除了你需要做好以下准备,我将它放在了./threejs-demo/scene/lib 目录中
- 三维功能定制;
- 接口封装,比如freshAttributes接口;
- 注册场景加载完成的回调事件addEventListener('sceneLoaded', ({ flag }) => )
- 键盘操作模式操作模式(无鼠标光标模式),快捷键为alt+1,除了此快捷键外,其他操作键可自定义,但需要在之后的编写中,给用户提示,此项要非常注意,因我们编辑器占用大量鼠标操作事件,因此编辑模式下场景的中移动的操作,不建议以鼠标为主
第三步:准备数据修改的数据仓库,数据仓库我们用的是 pinia,我将该文件的命名为 sceneRenderBackstage.js,并放在./threejs-demo/stroe/ 目录下
import {defineStore} from 'pinia';
import {shallowRef} from 'vue'
//import { SceneBackstage } from '@/engines/lib/main'
import {SceneBackstage} from '../scene/lib/engine.js';
export const useSceneDemo = defineStore('sceneDemo', {
state: () => {
return {
scene: shallowRef(null), //返回场景对象
};
},
actions: {
//功能:创建SceneRender,并返回场景对象
createSceneRender(containerDiv) {
this.scene = new SceneBackstage(containerDiv); //创建场景,并返回场景对象
},
//功能:注册场景加载完成的回调,一般用于设置历史数据或默认数据
initData(fn) {
this.scene.addEventListener('sceneLoaded', fn)
},
//功能:设置数据面板的样式方法
setColor(textColor
, bgColor, fontSize) {
if (this?.scene?.hotspotMgr) {
this.scene.hotspotMgr.models.forEach((key, wal) => {
key.textColor = textColor
key.bgColor = bgColor
key.fontSize = fontSize
key.update()
})
}
},
//功能:获取数据面板的样式方法
getColor() {
let obj = {
fontSize: 30,
textColor: 'fff',
bgColor: '#00AEFF',
}
console.log(this.scene.hotspotMgr.models, "32832139213")
this.scene.hotspotMgr.models.forEach((key, wal) => {
obj.textColor = key.textColor
obj.bgColor = key.bgColor
obj.fontSize = key.fontSize
})
return obj
},
//功能:修改绑定的数据,调用案例2
freshSensors(sensorValue) {
//可以更新单个,或者同时更新一组设备数据
//设备对应三维中的从左向右进行ID匹配
if (!sensorValue) {
let sensorValue = [
{sensorId: 1, gatherTime: '2023-06-01 15:48:08', gatherValue: 98.71},
{sensorId: 2, gatherTime: '2023-06-01 15:48:08', gatherValue: 1.83},
{sensorId: 5, gatherTime: '2023-06-01 15:48:08', gatherValue: 84.96},
{sensorId: 6, gatherTime: '2023-06-01 15:48:08', gatherValue: 176.41},
{sensorId: 7, gatherTime: '2023-06-01 15:48:08', gatherValue: 16.4}
];
}
this.scene.freshSensors(sensorValue);
return sensorValue
},
//功能:动态数据更新,调用案例1
freshAttributes(AttributesObj) {
//单个
let AttributesObj1 = {cylinderName: "二氧化碳气瓶"};
//this.scene.freshAttributes(AttributesObj1);
//多个
if (!AttributesObj) {
let AttributesObj = {
cylinderName: "二氧化碳气瓶",
pumpPower: '可变负压抽采泵'
}
}
this.scene.freshAttributes(AttributesObj);
return AttributesObj
},
},
});
第四步:编写属性面板Attribute.vue
此文件为固定模板,按实际情况修改你的属性表单即可,我们会将这个表单以style这个属性传入主文件main.vue的props中 你将在第六步看见我们如何使用他们 请注意示例代码中/*** 的地方,这些注释会对你的开发有帮助
<template>
<el-collapse v-model="activeNames">
<el-collapse-item title="样式" name="style">
<!-- 根据你开发的样式编辑能力和接口进行表单设计-->
<el-form v-model="formData">
<el-form-item label="字体大小">
<el-input v-model="formData.fontSize"></el-input>
</el-form-item>
<el-form-item label="字体颜色">
<tp-color-picker v-model="formData.color"/>
</el-form-item>
<el-form-item label="背景颜色">
<tp-color-picker v-model="formData.bgColor"/>
</el-form-item>
</el-form>
</el-collapse-item>
</el-collapse>
</template>
<script lang="ts">
import {defineComponent} from "vue";
import {useSceneDemo} from "@/plugins/tp-plugin/threejs-demo2/store/sceneRenderBackstage";
const sceneStore = useSceneDemo()
export default defineComponent({
data() {
return {
activeNames: 'style',
formData: {
fontSize: 30,
color: '',
bgColor: '',
}
}
},
watch: {
formData: {
handler(val) {
this.$emit("onChange", {
style: val
});
},
deep: true
}
}
,
mounted() {
if (this.formData.color === '') {
//*** 此处为获取你的默认值方法,如果没有该方法,你可以直接再上面的表单数据中写死 ****
let obj = sceneStore.getColor()
this.formData.fontSize = obj.fontSize
this.formData.color = obj.textColor
this.formData.bgColor = obj.bgColor
}
}
})
</script>
<style lang="scss" scoped>
</style>
第五步:编写数据面板:Data.vue
此文件也为固定模板,如果你对vue非常熟悉,你可以调整数据面板的结构和样式,否则请尽量不要修改其中的代码,我们会将改文件中的静态数据,设备绑定数据,分别用 value,data的属性,传入main.vue的props中,你将在第六步看见我们如何使用他们 请注意示例代码中/*** 的地方,这些注释会对你的开发有帮助
<template>
<div style="height:100%">
<el-row style="margin-bottom: 10px">
<el-radio-group v-model="formData.bindType">
<el-radio v-for="item in bindOptions" :label="item.value" size="small">{{ item.label }}</el-radio>
</el-radio-group>
</el-row>
<el-row style="height:100%">
<!-- 静态数据 -->
<el-input v-if="formData.bindType==='static'" :rows="20" type="textarea" v-model="formData.static"></el-input>
<!-- 动态数据 -->
<el-form-item v-else-if="formData.bindType==='dynamic'" style="width:100%">
<el-input :rows="2" type="textarea" v-model="formData.dynamic"></el-input>
</el-form-item>
<!-- 设备数据 -->
<div class="w-full" v-else-if="formData.bindType==='device'">
<slot></slot>
</div>
</el-row>
</div>
</template>
<script setup>
import {onMounted, onUnmounted, reactive, ref, watch} from "vue";
//***此处为静态数据的准备示例,这个地方,你改成你自己的数据结构即可,如没有,你可以给空对象,因为后面我们用到了这个变量,将它转换成了json字符串***
const staticData = {
Attributes: {
cylinderName: "二氧化碳气瓶",
pumpPower: '可变负压抽采泵'
},
Sensors: [
{sensorId: 1, gatherTime: '2023-06-01 15:48:08', gatherValue: 98.71},
{sensorId: 2, gatherTime: '2023-06-01 15:48:08', gatherValue: 1.83},
{sensorId: 5, gatherTime: '2023-06-01 15:48:08', gatherValue: 84.96},
{sensorId: 6, gatherTime: '2023-06-01 15:48:08', gatherValue: 176.41},
{sensorId: 7, gatherTime: '2023-06-01 15:48:08', gatherValue: 16.4}
]
}
//以下为固定写法,如无必要请勿修改
const emit = defineEmits(['onChange'])
const props = defineProps({
data: {
type: [String, Object],
default: () => ({})
}
});
const timers = ref([])
const formData = reactive({
bindType: 'static',
static: JSON.stringify(staticData)
})
const formData2 = reactive({
bindType: 'device',
device: null
})
const bindOptions = ref([
{value: 'static', label: '静态数据'},
{value: 'dynamic', label: '动态数据'},
{value: 'device', label: '设备数据'}
])
watch(formData, (newValues, oldValues) => {
console.log(newValues, "4324324")
emit("onChange", {
data: {bindType: formData.bindType, ...newValues}
});
})
onMounted(() => {
if (JSON.stringify(props.data) !== "{}" && JSON.stringify(props.data) !== "[]") {
formData.device = JSON.parse(JSON.stringify(props.data));
}
})
onUnmounted(() => {
if (timers.value.length > 0) {
timers.value.map((i) => {
clearInterval(i)
})
}
})
</script>
<style lang="scss" scoped>
.el-radio.el-radio--small {
margin-right: 10px
}
</style>