This commit is contained in:
yyy 2025-02-21 10:59:43 +08:00
commit a23543522b
10 changed files with 425 additions and 119 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 554 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 732 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 419 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 855 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 856 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 794 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 666 B

View File

@ -2,7 +2,7 @@
export const vDrag = { export const vDrag = {
mounted(el, binding) { mounted(el, binding) {
const enableDrag = binding.value; // 获取指令绑定的值 const enableDrag = binding.value; // 获取指令绑定的值
if (!enableDrag) return; // 如果为 false则不执行后续拖拽逻辑 // if (!enableDrag) return; // 如果为 false则不执行后续拖拽逻辑
el.style.position = 'absolute'; el.style.position = 'absolute';
// 记录元素的初始位置 // 记录元素的初始位置
@ -39,8 +39,16 @@ export const vDrag = {
// 保存初始位置,以便后续还原 // 保存初始位置,以便后续还原
el.__vDragInitialTop = initialTop; el.__vDragInitialTop = initialTop;
el.__vDragInitialLeft = initialLeft; el.__vDragInitialLeft = initialLeft;
if(!enableDrag){
// 如果之前启用,现在禁用,移除 mousedown 事件监听器
el.removeEventListener('mousedown', el.__vDragMousedown);
// 确保在禁用时停止正在进行的拖拽
document.removeEventListener('mousemove', el.__vDragMousemove);
document.removeEventListener('mouseup', el.__vDragMouseup);
}
}, },
updated(el, binding) { updated(el, binding) {
console.log('会走这边吗');
const enableDrag = binding.value; const enableDrag = binding.value;
const prevEnableDrag = binding.oldValue; const prevEnableDrag = binding.oldValue;
@ -65,6 +73,7 @@ export const vDrag = {
// 定义一个还原位置的函数 // 定义一个还原位置的函数
export const resetDragPosition = (el) => { export const resetDragPosition = (el) => {
console.log(el);
if (el.__vDragInitialTop!== undefined && el.__vDragInitialLeft!== undefined) { if (el.__vDragInitialTop!== undefined && el.__vDragInitialLeft!== undefined) {
el.style.top = `${el.__vDragInitialTop}px`; el.style.top = `${el.__vDragInitialTop}px`;
el.style.left = `${el.__vDragInitialLeft}px`; el.style.left = `${el.__vDragInitialLeft}px`;

View File

@ -1,35 +1,17 @@
<template> <template>
<div class="affix-container" :style="{ height: (heightVal*radio ) + 'px'}"> <div
<!-- <el-affix target=".affix-container" :offset="80"> class="affix-container"
<span @click="resetPosition ">回到原点</span> id="indexpage-container"
<div class="affix-container-top"> :style="{ height: heightVal * radio + 'px',cursor:isDrag ? 'pointer' : 'default',scale:isSizeRaio,transformOrigin: 'center center' }"
<el-scrollbar> >
<div class="scrollbar-flex-content">
<el-dropdown <div
placement="bottom" class="indexpage-container"
v-for="(item, index) in list" v-if="imgUrl"
:key="index" v-drag="isDrag"
style="border: none" :style="{ scale: 1, transformOrigin: '0 0' }"
> ref="draggableElement"
<div class="scrollbar-demo-item"> >
{{ item.floor }}
</div>
<template #dropdown v-if="item.children && item.children.length">
<el-dropdown-menu style="width: 100px">
<el-dropdown-item
v-for="(p, i) in item.children"
:key="i"
@click="getMapData(p)"
>{{ p.area }}</el-dropdown-item
>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</el-scrollbar>
</div>
</el-affix> -->
<div class="indexpage-container" v-if="imgUrl" v-drag="true" :style="{scale:1,transformOrigin:'0 0'}" ref="draggableElement">
<div class="indexpage-container-box"> <div class="indexpage-container-box">
<img :src="imgUrl" alt="" class="indexpage-container-box-img" /> <img :src="imgUrl" alt="" class="indexpage-container-box-img" />
@ -51,17 +33,23 @@
> >
</div> --> </div> -->
<!-- 小车 --> <!-- 小车 -->
<div class="indexpage-car-item" <div
v-for="(item, index) in testCarList" class="indexpage-car-item"
@dblclick="carDbClick(item,index)" v-for="(item, index) in testCarList"
:key="index" @dblclick="carDbClick(item, index)"
:style="{ :key="index"
:style="{
left: item.realX * radio + 'px', left: item.realX * radio + 'px',
top: item.realY * radio + 'px', top: item.realY * radio + 'px',
width: 40 * radio + 'px', width: 40 * radio + 'px',
height: 22 * radio + 'px', height: 22 * radio + 'px'
}"> }"
<img src="@/assets/imgs/indexPage/chache-4备份 7@2x.png" alt="" style="width: 100%; height: 100%" /> >
<img
src="@/assets/imgs/indexPage/chache-4备份 7@2x.png"
alt=""
style="width: 100%; height: 100%"
/>
</div> </div>
<div <div
class="indexpage-container-box-point-item" class="indexpage-container-box-point-item"
@ -135,20 +123,84 @@
</div> </div>
</div> </div>
</div> </div>
<!-- 左下角图例 -->
<div class="affix-container-left" :style="{ left: boxLeft + 'px' }">
<div class="affix-container-left-box">
<div class="affix-container-left-box-item-box" :style="{ height: legendObj.legendShow ? '84px' : '0', overflow: 'hidden',transition: 'all 0.3s ease-in-out'}">
<div class="affix-container-left-box-item">
<div class="affix-container-left-box-item-left"> 行驶路线 </div>
<img
src="@/assets/imgs/indexPage/yanjing_xianshi_o.png"
alt=""
class="affix-container-left-box-item-img"
v-if="legendObj.driveLineShow"
@click="changDriveLineShow"
/>
<img src="@/assets/imgs/indexPage/yanjing_yincang_o.png" alt="" class="affix-container-left-box-item-img" v-if="!legendObj.driveLineShow" @click="changDriveLineShow"/>
</div>
<div class="affix-container-left-box-item">
<div class="affix-container-left-box-item-left"> 车辆 </div>
<img
src="@/assets/imgs/indexPage/yanjing_xianshi_o.png"
alt=""
class="affix-container-left-box-item-img"
v-if="legendObj.carShow"
@click="changCarShow"
/>
<img src="@/assets/imgs/indexPage/yanjing_yincang_o.png" alt="" class="affix-container-left-box-item-img" v-if="!legendObj.carShow" @click="changCarShow"/>
</div>
</div>
<div class="affix-container-left-box-item-bottom" @click="legendObj.legendShow = !legendObj.legendShow">
<div class="affix-container-left-box-item-bottom-left">
图例
</div>
<img src="@/assets/imgs/indexPage/zhankai@2x.png" alt="" class="affix-container-left-box-item-bottom-img" :style="{transform: legendObj.legendShow ? 'rotate(180deg)' : 'rotate(0deg)',transition: 'all 0.3s ease-in-out'}"/>
</div>
</div>
</div>
<!-- 右下角的功能 -->
<div class="affix-container-right">
<!-- 拖拽 -->
<div class="affix-container-right-item" @click="changeIsDrag">
<img src="@/assets/imgs/indexPage/编组 12.png" alt="" style="width: 100%;height: 100%;" />
</div>
<!-- 放大 -->
<div class="affix-container-right-item">
<img src="@/assets/imgs/indexPage/编组 14.png" alt="" style="width: 100%;height: 100%;" @click="changeSizeRaio(0.2)"/>
</div>
<!-- 缩小 -->
<div class="affix-container-right-item">
<img src="@/assets/imgs/indexPage/编组 15.png" alt="" style="width: 100%;height: 100%;" @click="changeSizeRaio(-0.2)"/>
</div>
<!-- 全屏 -->
<div class="affix-container-right-item">
<img src="@/assets/imgs/indexPage/编组 22.png" alt="" style="width: 100%;height: 100%;" @click="toggleFullScreen" />
</div>
</div>
<storeDialog ref="storeDialogRef" @success="getList" /> <storeDialog ref="storeDialogRef" @success="getList" />
<carDialog ref="carDialogRef" /> <carDialog ref="carDialogRef" />
</template> </template>
<script setup> <script setup>
import { ref, defineComponent, reactive, nextTick, onMounted, onBeforeUnmount } from 'vue' import {
ref,
defineComponent,
reactive,
nextTick,
onMounted,
onBeforeUnmount,
onUnmounted,
} from 'vue'
import * as MapApi from '@/api/map/map' import * as MapApi from '@/api/map/map'
import WebSocketClient from '../webSocket.js' import WebSocketClient from '../webSocket.js'
import storeDialog from './storeDialog.vue' import storeDialog from './storeDialog.vue'
import { color } from 'echarts' import { color } from 'echarts'
import { resetDragPosition } from '@/utils/drag' import { resetDragPosition } from '@/utils/drag'
import carDialog from './carDialog.vue' import carDialog from './carDialog.vue'
import { is } from 'bpmn-js/lib/util/ModelUtil'
const imgUrl = ref('') const imgUrl = ref('')
const carDialogRef = ref(null) const carDialogRef = ref(null)
const socketClient = ref(null) const socketClient = ref(null)
const emit = defineEmits(['transmitMapId']) const emit = defineEmits(['transmitMapId'])
@ -157,7 +209,11 @@ const list = ref([])
const nowObject = ref(null) const nowObject = ref(null)
const testCarList = ref([]) const testCarList = ref([])
const carPointListFun = () => { const carPointListFun = () => {
let testJson = {"type":"map_push","content":"{\"d0:65:78:c4:af:cc\":\"{\\\"id\\\":1,\\\"macAddress\\\":\\\"d0:65:78:c4:af:cc\\\",\\\"robotModelNumber\\\":\\\"A-1\\\",\\\"pose2d\\\":{\\\"y\\\":\\\"1\\\",\\\"x\\\":\\\"2\\\",\\\"yaw\\\":\\\"30\\\",\\\"floor\\\":\\\"1\\\",\\\"area\\\":\\\"A区\\\",\\\"bat_soc\\\":\\\"40\\\"}}\"}"} let testJson = {
type: 'map_push',
content:
'{"d0:65:78:c4:af:cc":"{\\"id\\":1,\\"macAddress\\":\\"d0:65:78:c4:af:cc\\",\\"robotModelNumber\\":\\"A-1\\",\\"pose2d\\":{\\"y\\":\\"1\\",\\"x\\":\\"2\\",\\"yaw\\":\\"30\\",\\"floor\\":\\"1\\",\\"area\\":\\"A区\\",\\"bat_soc\\":\\"40\\"}}"}'
}
let data = JSON.parse(testJson.content) let data = JSON.parse(testJson.content)
// console.log("============",data) // console.log("============",data)
let dataList = [] let dataList = []
@ -167,12 +223,125 @@ const carPointListFun = () => {
data: JSON.parse(data[key]) data: JSON.parse(data[key])
}) })
} }
console.log("=====",dataList) console.log('=====', dataList)
testCarList.value = dataList testCarList.value = dataList
// let data2 = JSON.parse(data['d0:65:78:c4:af:cc']) // let data2 = JSON.parse(data['d0:65:78:c4:af:cc'])
// console.log(data2) // console.log(data2)
} }
//
const isDrag = ref(false)
const changeIsDrag = () => {
nextTick(() => {
isDrag.value = !isDrag.value
console.log(isDrag.value)
if(!isDrag.value){
//
resetPosition()
}
})
}
//
const isSizeRaio = ref(1)
const changeSizeRaio = (type) => {
if(type<0 && (isSizeRaio.value + type) <= 0){
return
}
isSizeRaio.value += type
}
//
const legendObj = reactive({
driveLineShow:false,
carShow:false,
legendShow:true
})
//
const changCarShow = () => {
legendObj.carShow = !legendObj.carShow
}
// 线
const changDriveLineShow = () => {
legendObj.driveLineShow = !legendObj.driveLineShow
}
const toggleFullScreen = () => {
var elem = document.getElementById("indexpage-container"); //
if (!document.fullscreenElement) { //
if (elem.requestFullscreen) { // requestFullscreen API
elem.requestFullscreen().catch(err => {
alert(`无法进入全屏模式: ${err.message}`); //
});
} else if (elem.mozRequestFullScreen) { // FirefoxAPI
elem.mozRequestFullScreen();
} else if (elem.webkitRequestFullscreen) { // WebKit/Safari/ChromeAPI
elem.webkitRequestFullscreen();
} else if (elem.msRequestFullscreen) { // IE/EdgeAPI
elem.msRequestFullscreen();
}
isDrag.value = true
} else { // 退
if (document.exitFullscreen) { // API退
document.exitFullscreen();
} else if (document.mozCancelFullScreen) { // FirefoxAPI
document.mozCancelFullScreen();
} else if (document.webkitExitFullscreen) { // WebKit/Safari/ChromeAPI
document.webkitExitFullscreen();
} else if (document.msExitFullscreen) { // IE/EdgeAPI
document.msExitFullscreen();
}
console.log('退出全屏')
isDrag.value = false
resetPosition()
}
//
document.addEventListener('fullscreenchange', function () {
if (!document.fullscreenElement) {
console.log('已退出全屏模式');
// 退
isDrag.value = false
resetPosition()
} else {
console.log('已进入全屏模式');
//
isDrag.value = true
}
});
//
document.addEventListener('mozfullscreenchange', function () {
if (!document.mozFullScreenElement) {
console.log('已退出全屏模式 (Firefox旧版)');
isDrag.value = false
resetPosition()
} else {
console.log('已进入全屏模式 (Firefox旧版)');
isDrag.value = true
}
});
document.addEventListener('webkitfullscreenchange', function () {
if (!document.webkitFullscreenElement) {
console.log('已退出全屏模式 (WebKit旧版)');
isDrag.value = false
resetPosition()
} else {
console.log('已进入全屏模式 (WebKit旧版)');
isDrag.value = true
}
});
document.addEventListener('msfullscreenchange', function () {
if (!document.msFullscreenElement) {
console.log('已退出全屏模式 (IE/Edge旧版)');
isDrag.value = false
resetPosition()
} else {
console.log('已进入全屏模式 (IE/Edge旧版)');
isDrag.value = true
}
});
}
// //
const getList = async () => { const getList = async () => {
let data = await MapApi.getPositionMapGetMap() let data = await MapApi.getPositionMapGetMap()
@ -185,15 +354,15 @@ const getList = async () => {
children: data[key] children: data[key]
}) })
} }
if(mapList.length){ if (mapList.length) {
mapList.forEach(item => { mapList.forEach((item) => {
if(item.children.length){ if (item.children.length) {
item.children.forEach(child => { item.children.forEach((child) => {
child.label = child.area child.label = child.area
child.value = child.id child.value = child.id
}) })
} }
}) })
} }
list.value = mapList list.value = mapList
@ -252,10 +421,10 @@ const getPositionMapListFun = async (positionMapId) => {
} }
const draggableElement = ref(null) const draggableElement = ref(null)
const resetPosition = () => { const resetPosition = () => {
if (draggableElement.value) { if (draggableElement.value) {
resetDragPosition(draggableElement.value); resetDragPosition(draggableElement.value)
} }
}; }
const calculateDistanceAndAngle = (point1, point2) => { const calculateDistanceAndAngle = (point1, point2) => {
// //
const dx = point2.left - point1.left const dx = point2.left - point1.left
@ -319,7 +488,7 @@ const disconnect = () => {
} }
// //
const getMapData = async (item) => { const getMapData = async (item) => {
console.log("============",item) // console.log("============",item)
nowObject.value = JSON.parse(JSON.stringify(item)) nowObject.value = JSON.parse(JSON.stringify(item))
let websoketUrl = `${replaceHttpWithWs(import.meta.env.VITE_BASE_URL)}/infra/ws?type=map&floor=${nowObject.value.floor}&area=${nowObject.value.area}` let websoketUrl = `${replaceHttpWithWs(import.meta.env.VITE_BASE_URL)}/infra/ws?type=map&floor=${nowObject.value.floor}&area=${nowObject.value.area}`
console.log(websoketUrl) console.log(websoketUrl)
@ -343,28 +512,33 @@ const radio = ref(1)
const computedRatio = () => { const computedRatio = () => {
nextTick(() => { nextTick(() => {
if (imgUrl.value) { if (imgUrl.value) {
// yaml
// yaml getImageSize(imgUrl.value)
getImageSize(imgUrl.value) .then(({ width, height }) => {
.then(({ width, height }) => { // console.log("",JSON.parse(nowObject.value.yamlJson))
// console.log("",JSON.parse(nowObject.value.yamlJson)) if (testCarList.value.length) {
if(testCarList.value.length){ testCarList.value.forEach((item) => {
testCarList.value.forEach((item) => { item.originWidth = width
item.originWidth = width item.originHeight = height
item.originHeight = height item.origin = JSON.parse(nowObject.value.yamlJson).origin
item.origin = JSON.parse(nowObject.value.yamlJson).origin item.realX = convertToFrontendCoordinates(item.origin, width, height, [
item.realX = convertToFrontendCoordinates(item.origin, width, height, [item.data.pose2d.x, item.data.pose2d.y]).left item.data.pose2d.x,
item.realY = convertToFrontendCoordinates(item.origin, width, height, [item.data.pose2d.x, item.data.pose2d.y]).top item.data.pose2d.y
}) ]).left
console.log("当前数据",testCarList.value) item.realY = convertToFrontendCoordinates(item.origin, width, height, [
} item.data.pose2d.x,
}) item.data.pose2d.y
.catch((error) => { ]).top
console.error(error.message); })
}); console.log('当前数据', testCarList.value)
}
})
.catch((error) => {
console.error(error.message)
})
let width = getElementWidthByClass('indexpage-container') let width = getElementWidthByClass('indexpage-container')
getImageWidth(imgUrl.value,'width').then((res) => { getImageWidth(imgUrl.value, 'width').then((res) => {
// console.log(res) // console.log(res)
let ratioVal = width / res let ratioVal = width / res
radio.value = ratioVal radio.value = ratioVal
@ -374,15 +548,14 @@ const computedRatio = () => {
item.radio = radio.value item.radio = radio.value
}) })
} }
if(testCarList.value.length){ if (testCarList.value.length) {
testCarList.value.forEach((item) => { testCarList.value.forEach((item) => {
item.radio = radio.value item.radio = radio.value
}) })
} }
}) })
getImageWidth(imgUrl.value,'height').then((res) => { getImageWidth(imgUrl.value, 'height').then((res) => {
console.log("高",res) console.log('高', res)
heightVal.value = res heightVal.value = res
}) })
// console.log(width) // console.log(width)
@ -390,45 +563,45 @@ const computedRatio = () => {
}) })
} }
const getImageSize = (url) =>{ const getImageSize = (url) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const img = new Image(); const img = new Image()
img.src = url; img.src = url
// //
img.onload = () => { img.onload = () => {
const width = img.width; const width = img.width
const height = img.height; const height = img.height
resolve({ width, height }); resolve({ width, height })
}; }
// //
img.onerror = () => { img.onerror = () => {
reject(new Error(`Failed to load image from URL 获取图片尺寸失败: ${url}`)); reject(new Error(`Failed to load image from URL 获取图片尺寸失败: ${url}`))
}; }
}); })
} }
const convertToFrontendCoordinates = (origin, width, height, target) => { const convertToFrontendCoordinates = (origin, width, height, target) => {
// x y // x y
const [originX, originY] = origin; const [originX, originY] = origin
// x y // x y
const [targetX, targetY] = target; const [targetX, targetY] = target
// x // x
const offsetX = targetX - originX; const offsetX = targetX - originX
// y // y
const offsetY = targetY - originY; const offsetY = targetY - originY
// left // left
const left = offsetX; const left = offsetX
// top // top
const top = height - offsetY; const top = height - offsetY
return { return {
top, top,
left, left,
origin origin
}; }
} }
const getImageWidth = (imageUrl,name) => { const getImageWidth = (imageUrl, name) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const img = new Image() const img = new Image()
img.onload = function () { img.onload = function () {
@ -451,19 +624,62 @@ const getElementWidthByClass = (className) => {
return null return null
} }
// //
const carDbClick = (item,index) => { const carDbClick = (item, index) => {
console.log(item) console.log(item)
carDialogRef.value.open(JSON.parse(JSON.stringify(item))) carDialogRef.value.open(JSON.parse(JSON.stringify(item)))
} }
const boxLeft = ref(0)
//
const getLeftPx = () => {
let indexpageContainer = document.getElementById('indexpage-container')
console.log('距离左边的距离', indexpageContainer.getBoundingClientRect().left)
boxLeft.value = indexpageContainer.getBoundingClientRect().left + 6
}
//
const observeWidthChange = (id, callback) => {
const targetElement = document.getElementById(id)
if (!targetElement) {
console.error(`未找到 ID 为 ${id} 的元素`)
return
}
const resizeObserver = new ResizeObserver((entries) => {
for (const entry of entries) {
const newWidth = entry.contentRect.width
callback(newWidth)
}
})
resizeObserver.observe(targetElement)
return () => {
resizeObserver.disconnect()
}
}
let stopObserving
defineExpose({ getMapData }) // open defineExpose({ getMapData }) // open
onMounted(() => { onMounted(() => {
carPointListFun() carPointListFun()
// getList() // getList()
window.addEventListener('resize', computedRatio) window.addEventListener('resize', computedRatio)
stopObserving = observeWidthChange('indexpage-container', (newWidth) => {
//
// console.log(`: ${newWidth}px`);
//
getLeftPx()
computedRatio()
})
}) })
onBeforeUnmount(() => { onBeforeUnmount(() => {
window.removeEventListener('resize', computedRatio) window.removeEventListener('resize', computedRatio)
}) })
//
onUnmounted(() => {
if (stopObserving) {
stopObserving()
}
})
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@ -471,7 +687,7 @@ onBeforeUnmount(() => {
width: 100%; width: 100%;
position: relative; position: relative;
overflow: hidden; overflow: hidden;
background: rgba(0,0,0,0.8); background: rgba(0, 0, 0, 0.8);
} }
.indexpage-container { .indexpage-container {
width: 100%; width: 100%;
@ -547,4 +763,84 @@ onBeforeUnmount(() => {
position: absolute; position: absolute;
cursor: pointer; cursor: pointer;
} }
.affix-container-left {
position: fixed;
bottom: 26px;
z-index: 999;
// overflow: hidden;
}
.affix-container-left-box {
width: 144px;
// height: 120px;
// background: #ffffff;
}
.affix-container-left-box-item-box {
width: 100%;
border-bottom: 1px solid #eeeeee;
background: #ffffff;
}
.affix-container-left-box-item {
width: 100%;
padding: 0 18px;
height: 42px;
display: flex;
align-items: center;
justify-content: space-between;
}
.affix-container-left-box-item-left {
font-family:
PingFangSC,
PingFang SC;
font-weight: 400;
font-size: 14px;
color: rgba(0, 0, 0, 0.88);
}
.affix-container-left-box-item-img {
cursor: pointer;
flex-shrink: 0;
width: 20px;
height: 12px;
}
.affix-container-left-box-item-bottom {
width: 100%;
height: 36px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
background: #ffffff;
}
.affix-container-left-box-item-bottom-img {
cursor: pointer;
flex-shrink: 0;
width: 12px;
height: 7px;
}
.affix-container-left-box-item-bottom-left {
cursor: pointer;
flex-shrink: 0;
font-family:
PingFangSC,
PingFang SC;
font-weight: 400;
font-size: 16px;
color: #98a4bf;
margin-right: 2px;
}
.affix-container-right{
position: fixed;
z-index: 999;
right: 40px;
bottom: 20px;
display: flex;
align-items: center;
justify-content: flex-end;
}
.affix-container-right-item{
width: 28px;
height: 28px;
cursor: pointer;
flex-shrink: 0;
margin-left: 8px;
}
</style> </style>

View File

@ -42,6 +42,7 @@ const downAgv = async () => {
const data = await MapApi.agvDownload() const data = await MapApi.agvDownload()
download.zip(data, `agv-${new Date().getTime()}.zip`) download.zip(data, `agv-${new Date().getTime()}.zip`)
} }
//
const findChildrenByValues = (tree, values) => { const findChildrenByValues = (tree, values) => {
if (!tree || tree.length === 0) { if (!tree || tree.length === 0) {
return null return null