模型信息树组件
功能介绍
通过 3DTiles 模型的属性信息及构件ID,能够生成该模型的信息树(构件树)。信息树不仅提供可视化展示构件层级的功能,还能够通过按钮控制对应构件类别的显隐。
你可以使用 图层管理树组件 代替此组件。
注:该功能使用了 elementUI 库,使用前需安装该库,具体方法见 elementUI 安装 。
注:该功能使用了 vue-giant-tree 库,使用前需安装该库(该组件库有BUG,如遇到报错可尝试先安装该组件库0.1.0版本,后安装该组件库1.0.0版本):
sh
npm install vue-giant-tree@1.0.0不妨通过代码示例在 Vue 中尝试一下:
在线演示
点击 在线链接 以查看在线演示。
组件代码示例
默认路径为 components/ModelTreeSet/index.vue
vue
<template>
<div>
<!-- 模型信息树 -->
<el-card class="box-card">
<div
id="move-layer"
class="title"
@mousedown="mousedown"
@mouseup="mouseup"
>
模型信息树
</div>
<hr />
<tree
:setting="setting"
:nodes="nodes"
@onCheck="onCheck"
@onCreated="handleCreated"
/>
</el-card>
</div>
</template>
<script type="text/javascript">
import tree from 'vue-giant-tree'
export default {
components: {
tree
},
data () {
return {
setting: {
check: {
enable: true
},
data: {
simpleData: {
enable: true,
pIdKey: 'pid'
}
},
view: {
showIcon: false,
addHoverDom: this.addHoverDom,
removeHoverDom: this.removeHoverDom
}
},
nodes: []
}
},
methods: {
initNodes () {
let myNodes = []
const level = []
const category = []
const family = []
window.nodesList.forEach((e, index) => {
// 将第一个index设为1开始,将0留给pid使用
index += 1
let newNode = {};
newNode.id = index;
newNode.pid = 0;
newNode.name = e.id;
newNode.checked = true;
newNode.open = false;
this.nodes.push(newNode);
uniCore.model.fetchPropertys(e.propertysURL).then((data) => {
data.forEach((ele, i) => {
// 这里可以设置radio变量为按楼层过滤、按类名过滤或是按族family过滤
let radio = '按楼层过滤';
// 首先将level、category、family依次找到
let levelRadio, categoryRadio, familyRadio
if (radio == '按楼层过滤') {
levelRadio = ele.level
categoryRadio = ele.category
familyRadio = ele.family
} else if (radio == '按类名过滤') {
levelRadio = ''
categoryRadio = ele.category
familyRadio = ele.family
} else {
levelRadio = ''
categoryRadio = ''
familyRadio = ele.family
}
if (levelRadio != '') {
// 找到level,将没记录的作为父级元素
if (ele.hasOwnProperty('level') && level.indexOf(index + levelRadio) === -1) {
myNodes.pid = index
myNodes.id = myNodes.pid + levelRadio
myNodes.name = levelRadio
myNodes.open = false
myNodes.checked = true
level.push(myNodes.id)
this.nodes.push(myNodes)
myNodes = []
}
}
if (categoryRadio != '') {
// 找到category,将没记录的作为二级元素
if (ele.hasOwnProperty('category') && category.indexOf(index + levelRadio + categoryRadio) === -1) {
myNodes.pid = index + levelRadio
myNodes.id = myNodes.pid + categoryRadio
myNodes.name = categoryRadio
myNodes.open = false
myNodes.checked = true
category.push(myNodes.id)
this.nodes.push(myNodes)
myNodes = []
}
}
if (familyRadio != '') {
// 找到family,将没记录的作为三级元素
if (ele.hasOwnProperty('family') && family.indexOf(index + '' + levelRadio + categoryRadio + familyRadio) === -1) {
myNodes.pid = index + levelRadio + categoryRadio
myNodes.id = myNodes.pid + familyRadio
myNodes.name = familyRadio
myNodes.open = false
myNodes.checked = true
family.push(myNodes.id)
this.nodes.push(myNodes)
myNodes = []
}
}
// 将所有的子项都作为四级元素,自动匹配对应的父级元素
const item = []
item.pid = index + levelRadio + categoryRadio + familyRadio
item.id = item.pid + ele.name
item.open = false
item.name = ele.name
item.checked = true
item.instanceid = ele._BATCHID === undefined ? ele.ElementID : ele._BATCHID;
this.nodes.push(item)
})
})
});
},
onCheck (evt, treeId, treeNode) {
// 数组去重
function handleArr (arr) {
return ([...new Set(arr)])
}
// 找到节点下所有的子节点
let findChild = function (array) {
for (let i = 0; i < array.length; i++) {
if (array[i].hasOwnProperty("children")) {
findChild(array[i].children)
} else {
allClickInstanceid.push(array[i].instanceid)
}
}
}
// 递归找到节点最上层父节点
let findParent = function (array) {
return array.getParentNode() === null ? array : findParent(array.getParentNode());
}
// 最新返回的treeNode所点击到的所有elementID
let allClickInstanceid = []
if (treeNode.hasOwnProperty("children")) {
findChild(treeNode.children)
} else {
allClickInstanceid.push(treeNode.instanceid)
}
// 以下代码将应用上面所得到的id序列进行显隐操作
// 所展示的模型ID,依据为initNodes函数的index,据此找到allClickInstanceid对应的哪个模型,应对多模型的信息树情况
let parentId = findParent(treeNode).id;
let parentTileset = window.nodesList[parentId - 1].tileset;
// 初始化下selectElementID
window.selectElementID === undefined ? window.selectElementID = [] : window.selectElementID;
let hideElementList = window.selectElementID.find(e => { return e.id === parentTileset.debugPickedTile.id })
if (hideElementList === undefined) {
window.selectElementID.push({ id: parentTileset.debugPickedTile.id, eleID: [] })
hideElementList = window.selectElementID.find(e => { return e.id === parentTileset.debugPickedTile.id })
}
// 如果最新返回的treeNode为选中状态,那里面包含的elementID对应的构件都需要显示
if (!treeNode.checked) {
hideElementList.eleID.push(...allClickInstanceid)
// 数组去重
hideElementList.eleID = handleArr(hideElementList.eleID);
} else {
// 数组去重
hideElementList.eleID = handleArr(hideElementList.eleID);
// 如果最新返回的treeNode为取消选中状态,那里面包含的elementID对应的构件都需要隐藏
allClickInstanceid.forEach(e => {
hideElementList.eleID.splice(hideElementList.eleID.indexOf(e), 1)
})
}
// 找到与elementID关联的构件方法
parentTileset.tileVisible.addEventListener((tile) => {
let content = tile.content;
let featuresLength = content.featuresLength;
for (let i = 0; i < featuresLength; i++) {
let feature = content.getFeature(i);
let elementId = feature.getProperty("id")
if (hideElementList.eleID.indexOf(elementId) !== -1) {
// 获得与elementID关联的构件feature
feature.show = false;
} else {
feature.show = true;
}
}
// 优化性能,自动清除事件
setTimeout(() => {
parentTileset.tileVisible._listeners = [];
parentTileset.tileVisible._scopes = [];
})
});
},
handleCreated: function (ztreeObj) {
this.ztreeObj = ztreeObj;
// onCreated 中操作ztreeObj对象展开第一个节点
ztreeObj.expandNode(ztreeObj.getNodes()[0], true);
},
/**
* 鼠标与窗口拖动相关
*/
mousedown (event, id) {
if (document.elementFromPoint(event.clientX, event.clientY).id === 'move-layer') {
this.selectElement = document.elementFromPoint(event.clientX, event.clientY).parentNode.parentNode;
document.querySelectorAll('.box-card').forEach((e) => {
e.style.zIndex = 1000;
})
this.selectElement.style.zIndex = 1001;
var div1 = this.selectElement
this.selectElement.style.cursor = 'move'
this.isDowm = true
var distanceX = event.clientX - this.selectElement.offsetLeft
var distanceY = event.clientY - this.selectElement.offsetTop
document.onmousemove = function (ev) {
var oevent = ev || event
div1.style.left = oevent.clientX - distanceX + 'px'
div1.style.top = oevent.clientY - distanceY + 'px'
}
document.onmouseup = function () {
document.onmousemove = null
document.onmouseup = null
div1.style.cursor = 'default'
}
}
},
//鼠标抬起
mouseup () {
this.isMove = false;
this.selectElement = "null"
}
},
mounted () {
},
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
::v-deep .el-card__body {
padding: 20px 20px 0 20px;
}
::v-deep .box-card::-webkit-scrollbar {
display: none;
}
::v-deep .box-card {
position: absolute;
top: 3%;
left: 3%;
z-index: 1;
background: rgb(26 26 26 / 83%);
border: 1px solid rgba(255, 255, 255, 0.3);
box-shadow: 0px 24px 54px 0px rgba(35, 41, 50, 0.5);
border-radius: 10px;
padding: 0 24px 24px 0px;
margin-bottom: 12px;
-webkit-backdrop-filter: blur(10px);
backdrop-filter: blur(10px);
max-width: 370px;
max-height: 70%;
overflow-y: scroll;
transition: none;
user-select: none;
.el-table {
border-radius: 15px;
}
.title {
font-size: 18px;
font-weight: bold;
color: #fefeff;
display: block;
margin-left: 24px;
margin-bottom: 10px;
user-select: none;
overflow: hidden;
cursor: move;
}
hr {
margin-left: 24px;
margin-bottom: 10px;
border: none;
border-bottom: 1px solid #ffffff1a;
}
.button {
display: inline-flex;
margin: 5px 10px;
color: white;
background: #ffffff00;
border-radius: 5px;
cursor: pointer;
}
.ztree li a {
color: #bdbdbd;
}
}
</style>调用代码示例
vue
<template>
<div id="unicoreContainer">
<!-- 信息树窗口卡片开始 -->
<mtSet ref="mtSetId"></mtSet>
<!-- 信息树窗口卡片结束 -->
</div>
</template>
<script>
import { UniCore } from 'unicore-sdk'
import { config } from 'unicore-sdk/unicore.config'
import 'unicore-sdk/Widgets/widgets.css'
import mtSet from '@/components/ModelTreeSet/index.vue'; //模型信息树组件
export default {
components: {
mtSet
},
// 生命周期 - 挂载完成(可以访问DOM元素)
mounted () {
this.init();
},
// 方法集合
methods: {
/**
* 通用图形引擎初始化
*/
init () {
// 初始化UniCore
// 目前采用Cesium的地形&底图数据,这里配置Cesium的token
let accessToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIxNjEwMzI4My01MjBmLTQzYzktOGZiMS0wMDRhZjE0N2IyMGIiLCJpZCI6MTc1NzkyLCJpYXQiOjE3MTM3NzQ3OTh9.zU-R4MNvHr8rvn1v28PQfDImyutnpPF2lmEgGeSPckQ";
// 初始化unicore
let uniCore = new UniCore(config, accessToken);
uniCore.init("unicoreContainer");
window.uniCore = uniCore;
let viewer = uniCore.viewer;
// 视角初始化
uniCore.position.buildingPosition(viewer, [113.12380548015745, 28.250758831850005, 700], -20, -45, 1);
let options = {
id: '小别墅1号示例',
url: '../../assets/3Dtiles/sample3_方法2_小别墅属性(1)/tileset.json',
propertysURL: '../../assets/3Dtiles/sample3_方法2_小别墅属性(1)/01 小别墅.json'
}
//加载3dtiles
uniCore.model.createTileset(options.url, options).then(cityLeft => {
uniCore.model.changeModelPos(cityLeft, [113.12098820449636, 28.256150218457687, 130], [0, 0, 0], [23.8, 23.8, 23.8])
// 绑定信息树(配合ModelTreeSet组件使用
options.tileset = cityLeft;
if (!!!window.nodesList) {
window.nodesList = [];
}
window.nodesList.push(options)
// 原本设计是作为开关调用的,这里使用定时器先展示功能
setTimeout(() => {
// 信息树初始化
this.$refs.mtSetId.nodes = [];
this.$refs.mtSetId.initNodes();
}, 1000)
})
}
}
}
</script>
<style scoped>
#unicoreContainer {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow: hidden;
background: black;
}
</style>示例运行结果

调用代码示例中的关键代码
js
// 绑定信息树(配合ModelTreeSet组件使用
options.tileset = cityLeft;
if (!!!window.nodesList) {
window.nodesList = [];
}
window.nodesList.push(options)
// 原本设计是作为开关调用的,这里使用定时器先展示功能
setTimeout(() => {
// 信息树初始化
this.$refs.mtSetId.nodes = [];
this.$refs.mtSetId.initNodes();
}, 1000)