<template>
|
<div class="MapLayerManage" v-if="options.id !== 3">
|
<div class="header">
|
<div class="title">{{ options.name }}</div>
|
</div>
|
<div class="content">
|
<!-- <el-tree-->
|
<!-- ref="layerTreeRef"-->
|
<!-- :class="{ isTheTerritory: options.id === 3 }"-->
|
<!-- :props="defaultProps"-->
|
<!-- :data="options.children"-->
|
<!-- show-checkbox-->
|
<!-- node-key="id"-->
|
<!-- :expand-on-click-node="false"-->
|
<!-- :check-on-click-node="false"-->
|
<!-- :check-on-click-leaf="false"-->
|
<!-- @check="handleChange"-->
|
<!-- @node-click="handleTilesNodeClick"-->
|
<!-- :filter-node-method="filterNode"-->
|
<!-- />-->
|
<van-collapse v-model="activeName" accordion>
|
<van-collapse-item
|
v-for="item in treeList"
|
:key="item.id"
|
:name="item.id"
|
>
|
<!-- 自定义标题区域 -->
|
<template #title>
|
<div class="title-content">
|
<!-- 复选框 - 阻止事件冒泡 -->
|
<div class="checkbox-wrapper" @click.stop>
|
<van-checkbox
|
:model-value="item.isCheck"
|
@update:model-value="(val) => handleParentChange(item, val)"
|
shape="square"
|
>
|
</van-checkbox>
|
</div>
|
|
<!-- 名称 - 点击执行其他方法 -->
|
<div class="title-text" @click.stop="handleTilesNodeClick(item)">
|
{{ item.name }}
|
</div>
|
</div>
|
</template>
|
|
<!-- 子级内容 -->
|
<div class="item-child" v-for="child in item.children" :key="child.id">
|
<div>
|
<van-checkbox
|
:model-value="child.isCheck"
|
@update:model-value="(val) => handleChildChange(item, child, val)"
|
shape="square"
|
/>
|
</div>
|
<div class="child-name" @click="handleTilesNodeClick(child)">{{ child.name }}</div>
|
</div>
|
</van-collapse-item>
|
</van-collapse>
|
</div>
|
</div>
|
</template>
|
|
<script setup>
|
import L from 'leaflet'
|
import * as turf from '@turf/turf'
|
import { onMounted } from 'vue'
|
|
// const defaultProps = {
|
// label: 'name',
|
// children: 'children',
|
// }
|
const props = defineProps({
|
options: Object,
|
getCurMap: Function
|
})
|
|
let treeList = ref([])
|
const addCheckField = (data) => {
|
return data.map(item => {
|
// 处理父级
|
const newItem = {
|
...item,
|
isCheck: false
|
}
|
// 递归处理子级
|
if (item.children && Array.isArray(item.children)) {
|
newItem.children = addCheckField(item.children)
|
}
|
|
return newItem
|
})
|
}
|
treeList.value = addCheckField(props.options.children)
|
console.log('treeList.value', treeList.value)
|
const activeName = ref('')
|
//=================================
|
|
// 处理父级复选框变化
|
const handleParentChange = (item, isChecked) => {
|
console.log('父级变化:', item.name, isChecked)
|
// 更新父级状态
|
item.isCheck = isChecked
|
|
// 更新所有子级状态与父级一致
|
if (item.children && item.children.length > 0) {
|
item.children.forEach(child => {
|
child.isCheck = isChecked
|
})
|
}
|
// 触发 check 事件
|
triggerCheckEvent()
|
}
|
|
// 处理子级复选框变化
|
const handleChildChange = (item, child, isChecked) => {
|
console.log('子级变化:', child.name, isChecked)
|
|
// 更新子级状态
|
child.isCheck = isChecked
|
|
// 更新父级状态
|
updateParentState(item)
|
|
// 触发 check 事件
|
triggerCheckEvent()
|
}
|
|
// 更新父级状态(根据子级选中情况)
|
const updateParentState = (item) => {
|
if (!item.children || item.children.length === 0) {
|
item.isCheck = false
|
return
|
}
|
|
// 检查是否所有子级都被选中
|
const allChildrenChecked = item.children.every(child => child.isCheck)
|
// 检查是否有至少一个子级被选中
|
const someChildrenChecked = item.children.some(child => child.isCheck)
|
|
item.isCheck = allChildrenChecked
|
}
|
|
// 获取选中的节点数据
|
const getCheckedData = () => {
|
const checkedKeys = []
|
const checkedNodes = []
|
const halfCheckedKeys = []
|
const halfCheckedNodes = []
|
|
// 获取所有选中的节点
|
treeList.value.forEach(item => {
|
// 检查父级是否选中
|
if (item.isCheck) {
|
checkedKeys.push(item.id)
|
checkedNodes.push(item)
|
}
|
|
// 检查子级
|
if (item.children && item.children.length > 0) {
|
item.children.forEach(child => {
|
if (child.isCheck) {
|
checkedKeys.push(child.id)
|
checkedNodes.push(child)
|
}
|
})
|
|
// 检查半选中状态(部分子级选中)
|
const checkedChildrenCount = item.children.filter(child => child.isCheck).length
|
if (checkedChildrenCount > 0 && checkedChildrenCount < item.children.length) {
|
halfCheckedKeys.push(item.id)
|
halfCheckedNodes.push(item)
|
}
|
}
|
})
|
|
return {
|
checkedKeys,
|
checkedNodes,
|
halfCheckedKeys,
|
halfCheckedNodes
|
}
|
}
|
|
// 触发 check 事件
|
const triggerCheckEvent = () => {
|
const checkedData = getCheckedData()
|
console.log('选中数据:', checkedData)
|
handleChange(checkedData)
|
}
|
//================end=================
|
|
|
let mapInstance = null
|
let mapLayerSource = null
|
|
mapInstance = props.getCurMap()
|
|
// 初始化图层
|
mapLayerSource = L.featureGroup()
|
mapInstance.addLayer(mapLayerSource)
|
|
// 渲染图斑
|
function renderingMapSpot() {
|
// 清空所有图层
|
if (mapLayerSource) {
|
mapLayerSource.clearLayers();
|
}
|
|
selectList.value
|
.filter(item => item.geo_data)
|
.forEach(item => {
|
const parsed = JSON.parse(item.geo_data); // 转为数组
|
const grouped = parsed.flat(); // 再展开
|
|
// 3. 计算多边形质心
|
const turfPolygon = turf.polygon([parsed]);
|
const center = turf.centerOfMass(turfPolygon);
|
const centerLatLng = [center.geometry.coordinates[1], center.geometry.coordinates[0]];
|
|
const labelMarker = L.marker(centerLatLng, {
|
icon: L.divIcon({
|
className: 'polygon-label',
|
html: `
|
<div style="
|
color: #008013;
|
font-weight: bold;
|
font-size: 12px;
|
font-family: 'Source Han Sans CN', 'Source Han Sans CN';
|
white-space: nowrap;
|
">
|
${item.name || '未命名'}
|
</div>
|
`,
|
iconSize: null, // 让div决定大小
|
iconAnchor: null // 不需要锚点,用CSS transform居中
|
}),
|
interactive: false, // 标签不参与交互
|
// zIndexOffset: 1000 // 确保标签在最上层
|
});
|
|
// 将坐标数组转换为 Leaflet 格式 [lat, lng, lat, lng...] -> [[lat, lng], [lat, lng]...]
|
const latLngs = [];
|
for (let i = 0; i < grouped.length; i += 2) {
|
latLngs.push([grouped[i + 1], grouped[i]]); // 注意:Leaflet 是 [lat, lng] 顺序
|
}
|
|
// 创建多边形(填充区域)
|
const polygon = L.polygon(latLngs, {
|
color: props.options.color,
|
fillColor: props.options.color,
|
fillOpacity: 0.5,
|
weight: 0, // 隐藏多边形边框,使用折线作为边框
|
});
|
|
// 创建折线(边框)
|
const polyline = L.polyline([...latLngs, latLngs[0]], { // 闭合多边形
|
color: props.options.color,
|
weight: 1.5,
|
opacity: 1,
|
fill: false
|
});
|
|
// 将两个图层组合在一起
|
const group = L.featureGroup([polygon, polyline, labelMarker]);
|
// 添加到图层组
|
mapLayerSource.addLayer(group);
|
});
|
|
// 自动调整地图视野以显示所有图斑
|
// if (mapLayerSource.getLayers().length > 0) {
|
// mapInstance.fitBounds(mapLayerSource.getBounds());
|
// }
|
}
|
|
const selectList = ref([])
|
|
const handleChange = (checked) => {
|
console.log(checked, '勾中值')
|
selectList.value = checked.checkedNodes
|
renderingMapSpot()
|
}
|
|
const handleTilesNodeClick = (node, info) => {
|
console.log(node,info, '点中值')
|
if (node.isCheck) {
|
if (node.geo_data) {
|
const positionsData = JSON.parse(node.geo_data);
|
if (!positionsData.length) return;
|
|
// 单个位置飞行
|
flyVisual({ positionsData, map: mapInstance });
|
|
} else if (node.level === 2) {
|
let positionsData = [];
|
node.children.forEach(item => {
|
if (item.geo_data) {
|
const list = JSON.parse(item.geo_data);
|
if (!list.length) return;
|
positionsData = [...positionsData, ...list];
|
}
|
});
|
|
if (positionsData.length > 0) {
|
flyVisual({ positionsData, map: mapInstance });
|
}
|
}
|
}
|
}
|
|
// 统一的飞行函数
|
function flyVisual({ positionsData, map }) {
|
if (!positionsData.length) return;
|
// 判断是单个点还是多边形
|
// if (positionsData[0].length === 2 || positionsData[0].length === 3) {
|
if (positionsData[0].length === 1) {
|
// 单个坐标点
|
const firstCoord = positionsData[0];
|
const latLng = [firstCoord[1], firstCoord[0]]; // 转换为 [lat, lng]
|
|
const point = map.latLngToContainerPoint(latLng);
|
const offsetPoint = point.subtract([0,-1]);
|
const offsetLatLng = map.containerPointToLatLng(offsetPoint);
|
map.flyTo(offsetLatLng, 13, {
|
duration: 1,
|
easeLinearity: 0.2
|
});
|
|
} else {
|
// 多边形坐标数组
|
const leafletCoords = positionsData.map(coord => [coord[1], coord[0]]);
|
const bounds = L.latLngBounds(leafletCoords);
|
|
map.flyToBounds(bounds, {
|
padding: [30, 30],
|
duration: 2,
|
maxZoom: 13
|
});
|
}
|
}
|
|
const layerTreeRef = ref(null)
|
watch(
|
() => props.options.children,
|
() => {
|
nextTick(() => layerTreeRef.value?.filter(''))
|
},
|
{ immediate: true, deep: true }
|
)
|
|
|
function filterNode(value, data) {
|
if (props.options.id === 3) {
|
return data.level === 2 || data.level === 1
|
}
|
return true
|
}
|
|
function handleMapReady(map) {
|
mapInstance = map
|
|
// 初始化图层
|
mapLayerSource = L.featureGroup()
|
mapInstance.addLayer(mapLayerSource)
|
}
|
|
// let map = null
|
|
onMounted(() => {
|
|
})
|
|
onBeforeUnmount(() => {
|
mapLayerSource && mapLayerSource?.entities?.removeAll()
|
})
|
</script>
|
|
<style scoped lang="scss">
|
.MapLayerManage {
|
.header {
|
margin-top: 10px;
|
margin-bottom: 10px;
|
height: 20px;
|
font-family: Source Han Sans CN, Source Han Sans CN;
|
font-weight: bold;
|
font-size: 16px;
|
color: #222324;
|
line-height: 20px;
|
text-align: left;
|
font-style: normal;
|
text-transform: none;
|
}
|
.content {
|
//padding: 12px;
|
display: flex;
|
background: #fff;
|
border-radius: 16px;
|
.isTheTerritory {
|
:deep() {
|
.el-tree-node__expand-icon {
|
visibility: hidden;
|
}
|
}
|
}
|
}
|
:deep(.van-collapse) {
|
width: 100%;
|
.title-content {
|
display: flex;
|
.checkbox-wrapper {
|
margin-right: 10px;
|
}
|
}
|
.item-child {
|
display: flex;
|
//justify-content: space-between;
|
justify-items: center;
|
align-items: center;
|
height: 44px;
|
line-height: 44px;
|
margin-left: 30px;
|
}
|
.child-name {
|
margin-left: 10px;
|
font-size: 14px;
|
color: #222324;
|
}
|
}
|
|
:deep(.van-collapse *)::after {
|
border-color: #E3E3E3;
|
}
|
:deep([class*=van-hairline]:after) {
|
border-top: 1px solid #E3E3E3;
|
border-bottom: 1px solid #E3E3E3;
|
}
|
|
:deep(.van-collapse-item__content) {
|
padding: 0;
|
}
|
:deep(.van-checkbox__icon--checked .van-icon) {
|
background-color: transparent;
|
color: #1D6FE9;
|
font-weight: bolder;
|
}
|
:deep(.van-checkbox__icon .van-icon) {
|
border: 1px solid #F5F5F5;
|
}
|
:deep(.van-checkbox){
|
background: transparent;
|
}
|
}
|
</style>
|