地图组件进阶
本章介绍地图组件的普通点位的图层过滤
图层过滤
通过对 point-normal-list
的过滤来更改图层中的数据
<template>
<div class="layer">
<minemap map-dom-id="layer" :point-normal-list="pointNormalListFilter" />
<div class="map-options">
<div class="options-detail">
<div class="options-content">
<div class="layer-border top-left" />
<div class="layer-border top-right" />
<div class="layer-border bottom-left" />
<div class="layer-border bottom-right" />
<div class="all-layer">
<Checkbox
:indeterminate="indeterminate"
:model-value="checkAllLayer"
@on-change="handleCheckAllLayer"
>
<span style="color: #fff">全部图层</span>
</Checkbox>
</div>
<Checkbox
class="option-item"
v-for="(layer, index) in layerTypeList"
:key="index"
v-model="layer.checked"
@on-change="(val) => checkLayerChange(val, layer)"
>
<img class="layer-icon" v-if="layer.icon" :src="layer.icon" />
<span v-else class="layer-sign" :style="{ backgroundColor: layer.color }" />
<span style="color: #fff">{{ layer.layerName }}</span>
</Checkbox>
</div>
</div>
</div>
</div>
</template>
<script setup>
import minemap from './components/index.vue';
import { deepCopy } from '@web-basic-doc/utils';
import { ref, reactive } from 'vue';
// 动态引入图层icon
const getImage = (name) => {
return `/images/map/minemap/symbol-${name}-icon.png`;
};
const layerTypeList = reactive([
{
layerType: '1',
icon: getImage('video'),
layerName: '高清视频',
checked: true,
},
{
layerType: '11',
icon: getImage('face'),
layerName: '人脸卡口',
checked: true,
},
{
layerType: '2',
icon: getImage('vehicle'),
layerName: '车辆卡口',
checked: true,
},
{
layerType: '3',
icon: getImage('wifi'),
layerName: 'WiFi设备',
checked: true,
},
{
layerType: '5',
icon: getImage('rfid'),
layerName: 'RFID设备',
checked: true,
},
{
layerType: '4',
icon: getImage('fence'),
layerName: '电围设备',
checked: true,
},
]);
// 全选图层状态
let checkAllLayer = ref(true);
// 全选图层半选状态
let indeterminate = ref(false);
const pointNormalList = [
{
id: 1,
title: '海安设备1',
kind: '1',
lng: 120.468851,
lat: 32.555493,
icon: 'video', //必须为staticUtil中加载到地图中的图片
},
{
id: 2,
title: '海安设备2',
kind: '11',
lng: 120.491374,
lat: 32.538508,
icon: 'face',
},
{
id: 3,
title: '海安设备3',
kind: '5',
lng: 120.471218,
lat: 32.537631,
icon: 'rfid',
},
];
let pointNormalListFilter = ref([
{
id: 1,
title: '海安设备1',
kind: '1',
lng: 120.468851,
lat: 32.555493,
icon: 'video', //必须为staticUtil中加载到地图中的图片
},
{
id: 2,
title: '海安设备2',
kind: '11',
lng: 120.491374,
lat: 32.538508,
icon: 'face',
},
{
id: 3,
title: '海安设备3',
kind: '5',
lng: 120.471218,
lat: 32.537631,
icon: 'rfid',
},
]);
// 全选/取消全选 所有图层分类
const handleCheckAllLayer = (val) => {
if (val) {
checkAllLayer.value = true;
indeterminate.value = false;
layerTypeList.forEach((layer) => {
layer.checked = true;
});
// 普通点位图层
if (pointNormalList.length) {
pointNormalListFilter.value = deepCopy(pointNormalList);
}
} else {
checkAllLayer.value = false;
indeterminate.value = false;
layerTypeList.forEach((layer) => {
layer.checked = false;
});
if (pointNormalList.length) {
pointNormalListFilter.value = [];
}
}
};
const checkLayerChange = (val, layer) => {
// 点选其他图层
const checkedLayer = layerTypeList.filter((row) => row.checked);
// 全选的情况下
if (checkedLayer.length === layerTypeList.length) {
indeterminate.value = false;
checkAllLayer.value = true;
pointNormalListFilter.value = deepCopy(pointNormalList);
// 没有全选的情况下
} else if (checkedLayer.length > 0) {
indeterminate.value = true;
checkAllLayer.value = false;
if (val) {
//普通点位筛选
if (pointNormalList.length) {
pointNormalListFilter.value = pointNormalList.filter(
(point) => checkedLayer.findIndex((row) => row.layerType === point.kind) !== -1,
);
}
} else {
//普通点位筛选
if (pointNormalList.length) {
const removePointList = pointNormalListFilter.value.filter(
(point) => point.kind === layer.layerType,
);
removePointList.forEach((removePoint) => {
const pointIndex = pointNormalListFilter.value.findIndex(
(point) => point.id === removePoint.id,
);
pointIndex !== -1 && pointNormalListFilter.value.splice(pointIndex, 1);
});
}
}
// 全不选的情况下
} else {
indeterminate.value = false;
checkAllLayer.value = false;
pointNormalListFilter.value = [];
}
};
</script>
<style lang="scss" scoped>
.layer {
position: relative;
width: 100%;
height: 500px;
overflow: hidden;
}
.map-options {
position: absolute;
top: 10px;
right: 20px;
z-index: 3;
.options-detail {
position: relative;
margin-top: 15px;
color: #fff;
background-color: transparent;
.layer-border {
position: absolute;
width: 17px;
height: 17px;
&.top-left {
top: -6px;
left: -6px;
border-top: 2px solid #14ccff;
border-left: 2px solid #14ccff;
}
&.top-right {
top: -6px;
right: -6px;
border-top: 2px solid #14ccff;
border-right: 2px solid #14ccff;
}
&.bottom-left {
bottom: -6px;
left: -6px;
border-bottom: 2px solid #14ccff;
border-left: 2px solid #14ccff;
}
&.bottom-right {
right: -6px;
bottom: -6px;
border-right: 2px solid #14ccff;
border-bottom: 2px solid #14ccff;
}
}
.options-content {
background-color: rgb(2 11 44 / 70%);
border: 1px solid #083d7d;
box-shadow: inset 0 0 20px rgb(0 85 255 / 50%);
}
.all-layer {
position: relative;
display: flex;
height: 44px;
padding: 0 15px;
border-bottom: 1px solid rgb(6 128 251 / 40%);
align-items: center;
&::after,
&::before {
position: absolute;
right: 0;
bottom: 0;
display: inline-block;
width: 10px;
height: 1px;
background-color: #0680fb;
content: '';
}
&::before {
left: 0;
}
}
.option-item {
display: flex;
height: 40px;
padding: 0 15px;
align-items: center;
:deep(.ivu-checkbox-label-text) {
display: flex;
justify-content: center;
align-items: center;
}
.layer-sign {
display: inline-block;
width: 26px;
height: 2px;
margin-right: 10px;
margin-left: 5px;
border-radius: 1px;
}
.layer-icon {
width: 26px;
height: 30px;
margin-right: 10px;
margin-left: 5px;
}
}
}
}
</style>
树结构分类图层过滤
<template>
<div class="layer-tree-box">
<minemap map-dom-id="layer-tree" :point-normal-list="pointNormalListFilter" />
<div class="map-options">
<div class="options-detail">
<div class="options-content">
<div class="layer-border top-left" />
<div class="layer-border top-right" />
<div class="layer-border bottom-left" />
<div class="layer-border bottom-right" />
<div class="all-layer">
<Checkbox
:indeterminate="indeterminate"
:model-value="checkAllLayer"
@on-change="handleCheckAllLayer"
>
<span style="color: #fff">全部图层</span>
</Checkbox>
</div>
<el-tree
class="layer-tree"
:data="treeData"
:icon="layerIcon"
:props="{ class: customNodeClass }"
:default-checked-keys="defaultCheckedKeys"
ref="layerTreeRef"
empty-text="暂无数据"
node-key="typeCode"
default-expand-all
show-checkbox
@check="checkTree"
>
<template #default="{ node, data }">
<div class="layer-tree-item">
<img
v-if="data.iconName"
class="layer-icon vt-middle"
:src="getImage(data.iconName)"
/>
<span
class="inline vt-middle"
:style="{ fontWeight: node.level === 1 ? 900 : 500, color: '#fff' }"
>
{{ data.typeName }}
</span>
</div>
</template>
</el-tree>
</div>
</div>
</div>
</div>
</template>
<script setup>
import minemap from './components/index.vue';
import layerIcon from './components/layer-icon.vue';
import { deepCopy, arrayToJson } from '@web-basic-doc/utils';
import { computed, ref } from 'vue';
const layerTreeData = [
{
id: 1,
typeCode: '1001',
typeName: '公共服务场所',
parentId: -1,
iconName: null,
},
{
id: 2,
typeCode: '1002',
typeName: '特种行业',
parentId: -1,
iconName: null,
},
{
id: 3,
typeCode: '1003',
typeName: '新业态',
parentId: -1,
iconName: null,
},
{
id: 4,
typeCode: '100101',
typeName: '歌舞厅',
parentId: 1,
iconName: 'other',
},
{
id: 5,
typeCode: '100102',
typeName: '棋牌室',
parentId: 1,
iconName: 'chessCards',
},
{
id: 6,
typeCode: '100103',
typeName: '影剧院',
parentId: 1,
iconName: 'other',
},
{
id: 7,
typeCode: '100104',
typeName: '医院',
parentId: 1,
iconName: 'hospital',
},
{
id: 8,
typeCode: '100105',
typeName: '学校',
parentId: 1,
iconName: 'school',
},
{
id: 9,
typeCode: '100106',
typeName: '超市',
parentId: 1,
iconName: 'market',
},
{
id: 10,
typeCode: '100107',
typeName: '游泳馆',
parentId: 1,
iconName: 'swim',
},
{
id: 11,
typeCode: '100108',
typeName: '体育馆',
parentId: 1,
iconName: 'gym',
},
{
id: 12,
typeCode: '100201',
typeName: '旅馆',
parentId: 2,
iconName: 'hotel',
},
{
id: 13,
typeCode: '100202',
typeName: '印铸刻字',
parentId: 2,
iconName: 'other',
},
{
id: 14,
typeCode: '100203',
typeName: '旧货',
parentId: 2,
iconName: 'other',
},
{
id: 15,
typeCode: '100204',
typeName: '典当',
parentId: 2,
iconName: 'pawn',
},
{
id: 16,
typeCode: '100205',
typeName: '拍卖',
parentId: 2,
iconName: 'other',
},
{
id: 17,
typeCode: '100206',
typeName: '寄卖',
parentId: 2,
iconName: 'other',
},
{
id: 18,
typeCode: '100207',
typeName: '维修',
parentId: 2,
iconName: 'other',
},
{
id: 19,
typeCode: '100301',
typeName: '在线教育',
parentId: 3,
iconName: 'other',
},
{
id: 20,
typeCode: '100302',
typeName: '互联网医疗',
parentId: 3,
iconName: 'other',
},
{
id: 21,
typeCode: '100303',
typeName: '线上办公',
parentId: 3,
iconName: 'other',
},
{
id: 22,
typeCode: '100304',
typeName: '电商',
parentId: 3,
iconName: 'other',
},
{
id: 23,
typeCode: '100305',
typeName: '无人零售',
parentId: 3,
iconName: 'other',
},
{
id: 24,
typeCode: '100306',
typeName: '共享出行',
parentId: 3,
iconName: 'other',
},
];
const pointNormalList = [
{
id: 1,
title: '江苏省海安高级中学',
unitType: '1001',
unitSubType: '100105',
lng: 120.468851,
lat: 32.555493,
icon: 'school', //必须为staticUtil中加载到地图中的图片
},
{
id: 2,
title: '王府大酒店',
unitType: '1002',
unitSubType: '100201',
lng: 120.491374,
lat: 32.538508,
icon: 'hotel', //必须为staticUtil中加载到地图中的图片
},
{
id: 3,
title: '智达教育(海安校区)',
unitType: '1003',
unitSubType: '100301',
lng: 120.471218,
lat: 32.537631,
icon: 'other', //必须为staticUtil中加载到地图中的图片
},
];
const getImage = (name) => {
return `/images/map/minemap/symbol-${name}-icon.png`;
};
// 全选图层状态
let checkAllLayer = ref(true);
// 全选图层半选状态
let indeterminate = ref(false);
const customNodeClass = (data) => {
if (data.parentId === -1) {
return 'layer-item-type';
}
};
let pointNormalListFilter = ref([
{
id: 1,
title: '江苏省海安高级中学',
unitType: '1001',
unitSubType: '100105',
lng: 120.468851,
lat: 32.555493,
icon: 'school', //必须为staticUtil中加载到地图中的图片
},
{
id: 2,
title: '王府大酒店',
unitType: '1002',
unitSubType: '100201',
lng: 120.491374,
lat: 32.538508,
icon: 'hotel', //必须为staticUtil中加载到地图中的图片
},
{
id: 3,
title: '智达教育(海安校区)',
unitType: '1003',
unitSubType: '100301',
lng: 120.471218,
lat: 32.537631,
icon: 'other', //必须为staticUtil中加载到地图中的图片
},
]);
const layerTreeRef = ref(null);
// 树结构数据
let treeData = ref(arrayToJson(layerTreeData, 'id', 'parentId'));
// 默认选中树结构
let defaultCheckedKeys = computed(() => treeData.value.map((row) => row.typeCode));
// 选中树结构节点
const checkTree = () => {
const checkedKeys = layerTreeRef.value.getCheckedKeys(true);
const checkedPoint = [];
pointNormalList.forEach((point) => {
if (checkedKeys.includes(point.unitSubType)) {
checkedPoint.push(point);
}
});
pointNormalListFilter.value = checkedPoint;
// 判断是否全选
const checkedNum = layerTreeRef.value.getCheckedKeys().length;
if (checkedNum === 0) {
checkAllLayer.value = false;
indeterminate.value = false;
} else if (checkedNum === layerTreeData.length) {
checkAllLayer.value = true;
indeterminate.value = false;
} else {
checkAllLayer.value = false;
indeterminate.value = true;
}
};
// 全选/取消全选 所有图层分类
const handleCheckAllLayer = (val) => {
if (val) {
checkAllLayer.value = true;
indeterminate.value = false;
// 普通点位图层
if (pointNormalList.length) {
pointNormalListFilter.value = deepCopy(pointNormalList);
}
if (layerTreeData.length) {
layerTreeRef.value.setCheckedKeys(layerTreeData.map((row) => row.typeCode));
}
} else {
checkAllLayer.value = false;
indeterminate.value = false;
if (pointNormalList.length) {
pointNormalListFilter.value = [];
}
if (layerTreeData.length) {
layerTreeRef.value.setCheckedKeys([]);
}
}
};
</script>
<style lang="scss" scoped>
.layer-tree-box {
position: relative;
width: 100%;
height: 500px;
overflow: hidden;
}
.map-options {
position: absolute;
z-index: 3;
top: 10px;
right: 20px;
.options-detail {
margin-top: 15px;
color: #fff;
background-color: transparent;
position: relative;
.layer-border {
width: 17px;
height: 17px;
position: absolute;
&.top-left {
border-left: 2px solid #14ccff;
border-top: 2px solid #14ccff;
left: -6px;
top: -6px;
}
&.top-right {
border-right: 2px solid #14ccff;
border-top: 2px solid #14ccff;
right: -6px;
top: -6px;
}
&.bottom-left {
border-left: 2px solid #14ccff;
border-bottom: 2px solid #14ccff;
left: -6px;
bottom: -6px;
}
&.bottom-right {
border-right: 2px solid #14ccff;
border-bottom: 2px solid #14ccff;
right: -6px;
bottom: -6px;
}
}
.options-content {
background-color: rgba(2, 11, 44, 0.7);
box-shadow: inset 0 0 20px rgba(0, 85, 255, 0.5);
border: 1px solid #083d7d;
}
.all-layer {
padding: 0 15px;
height: 44px;
display: flex;
align-items: center;
position: relative;
border-bottom: 1px solid rgba(6, 128, 251, 0.4);
&::after,
&::before {
display: inline-block;
content: '';
position: absolute;
right: 0;
bottom: 0;
width: 10px;
height: 1px;
background-color: #0680fb;
}
&::before {
left: 0;
}
}
.layer-tree {
max-height: 300px;
overflow-y: auto;
:deep(.el-tree-node__content) {
padding: 5px 15px 5px 15px !important;
height: auto;
.is-leaf {
.el-icon {
color: transparent;
cursor: default;
}
}
}
:deep(.el-tree-node__expand-icon) {
position: absolute;
right: 0;
}
:deep(.layer-item-type) {
> .el-tree-node__content {
background-color: rgba(13, 47, 123, 0.7);
}
}
.layer-icon {
width: 26px;
height: 30px;
margin-right: 3px;
}
}
}
}
</style>