WholeProcessPlatform/frontend/src/components/mapLegend/index.vue

257 lines
6.6 KiB
Vue
Raw Normal View History

2026-03-31 14:17:30 +08:00
<template>
<div class="mapLegendView">
<div class="legendTitle">
图例
<span class="legendBtn" @click="isOpen = !isOpen">
<i
class="icon iconfont"
:class="isOpen ? 'icon-fold' : 'icon-unFold'"
></i>
2026-03-31 14:17:30 +08:00
</span>
</div>
<div class="legendContent" v-show="isOpen">
<a-spin :spinning="loadLegendData" tip="加载中...">
<div class="legendContainer">
<div
class="legendGroup"
v-for="i in legendData"
:key="i.name"
:class="{ envFac: i.name == '环保设施' }"
>
<div
class="groupTitle"
v-if="i.name != '环保设施'"
:class="{ gray: isGroupAllUnchecked(i) }"
@click="toggleGroup(i)"
>
{{ i.name }}
</div>
<div
class="groupContent"
:class="{ envFacBottom: hasEnvFac && i.name != '环保设施' }"
>
<template v-for="j in i.childrenList" :key="j.name">
<LegendItem
v-if="j.layerCode"
:item="j"
@click="legendItemClick(j)"
/>
<div
v-else-if="j.childrenList && j.childrenList.length > 0"
class="legendGroup"
>
<div
class="groupTitle"
:class="{ gray: isGroupAllUnchecked(j) }"
@click="toggleGroup(j)"
>
{{ j.name }}
</div>
<div class="groupContent">
<LegendItem
v-for="item in j.childrenList"
:key="item.layerCode"
:item="item"
@click="legendItemClick(item)"
/>
</div>
2026-04-03 16:04:16 +08:00
</div>
</template>
</div>
2026-03-31 14:17:30 +08:00
</div>
</div>
</a-spin>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue';
import { useMapOrchestrator } from '@/modules/map/application/map-orchestrator';
import { useMapDataStore } from '@/modules/map/stores/map-data.store';
import { useMapStore } from '@/store/modules/map';
import LegendItem from '@/components/mapLegend/LegendItem.vue';
const mapStore = useMapStore();
const mapDataStore = useMapDataStore();
const mapOrchestrator = useMapOrchestrator();
const isOpen = ref(true);
// 备注:图例组件直接渲染当前已派生好的图例运行态,不再维护本地同步副本。
const legendData = computed(() => mapStore.legendDataSelected || []);
// 备注loading 状态直接取数据缓存态,组件本身不再 watch 后再二次赋值。
const loadLegendData = computed(() => mapDataStore.loading);
// 备注:环保设施分组展示状态直接从当前图例数据派生,保持组件只负责渲染。
const hasEnvFac = computed(() =>
legendData.value.some((item: any) => item.name === '环保设施')
);
2026-04-03 16:04:16 +08:00
const legendItemClick = (item: any) => {
if (item.parentName == '环保设施') return;
2026-04-03 16:04:16 +08:00
if (item.canBeChecked == 0) {
return;
}
if (item.nameEn) {
mapOrchestrator.toggleLegend(item.nameEn, item.checked == 0 ? 1 : 0);
}
2026-04-03 16:04:16 +08:00
};
const getAllLeafItems = (group: any): any[] => {
const items: any[] = [];
if (!group.childrenList) return items;
group.childrenList.forEach((child: any) => {
if (child.layerCode) {
items.push(child);
} else if (child.childrenList && child.childrenList.length > 0) {
items.push(...getAllLeafItems(child));
}
});
return items;
};
const isGroupAllUnchecked = (group: any): boolean => {
const leafItems = getAllLeafItems(group);
if (leafItems.length === 0) return false;
return leafItems.every((item: any) => item.checked === 0);
};
const toggleGroup = (group: any) => {
const leafItems = getAllLeafItems(group);
if (leafItems.length === 0) return;
const hasAnyChecked = leafItems.some((item: any) => item.checked === 1);
const targetChecked = hasAnyChecked ? 0 : 1;
const targetNameEns = leafItems
.filter(
(item: any) => item.parentName !== '环保设施' && item.canBeChecked !== 0
)
.filter((item: any) => item.checked !== targetChecked)
.map((item: any) => item.nameEn)
.filter(Boolean);
mapOrchestrator.toggleLegendBatch(targetNameEns, targetChecked);
};
2026-03-31 14:17:30 +08:00
</script>
<style lang="scss" scoped>
.mapLegendView {
position: absolute;
left: 0;
bottom: 0;
min-width: 72px;
margin: 24px 0 16px 16px;
border: 1px solid #ccdae7;
padding: 6px;
z-index: 9;
background-color: #ffffff;
}
.legendTitle {
font-size: 16px;
font-weight: 700;
display: flex;
justify-content: space-between;
cursor: pointer;
.legendBtn {
cursor: pointer;
}
}
.envFac {
position: absolute;
left: 0;
bottom: 0;
background-color: #ffffff;
border-top: 1px solid #eeeeee;
.groupContent {
padding: 0 !important;
:deep(.legendItem) {
cursor: none;
&:hover {
.legendIconTitle {
cursor: auto;
color: rgba(0, 0, 0, 0.85);
}
}
}
}
}
.envFacBottom {
padding-bottom: 80px !important;
}
2026-03-31 14:17:30 +08:00
.legendContent {
white-space: nowrap;
max-width: 450px;
max-height: 450px;
2026-03-31 14:17:30 +08:00
overflow-x: scroll;
overflow-y: scroll;
border-top: 1px solid #eeeeee;
:deep(.ant-spin-nested-loading) {
min-height: 50px;
}
&::-webkit-scrollbar {
display: none;
}
.legendContainer {
position: relative;
min-height: 20px;
min-width: 30px;
overflow: auto;
padding-bottom: 8px;
}
2026-03-31 14:17:30 +08:00
.legendGroup {
display: inline-block;
vertical-align: top;
padding: 0 4px;
.groupTitle {
text-align: left;
font-weight: 700;
font-size: 14px;
cursor: pointer;
}
.groupContent {
font-size: 14px;
padding: 10px 0;
margin-bottom: 0;
.gray {
filter: grayscale(100%);
color: #00000073;
}
2026-04-03 16:04:16 +08:00
.disabled {
cursor: not-allowed !important;
2026-03-31 14:17:30 +08:00
}
}
}
}
.legendItem {
cursor: pointer;
margin-right: 10px;
padding: 4px 0;
min-height: 30px;
display: flex;
flex-direction: row;
.legendIcon {
width: 25px !important;
height: 25px !important;
text-align: center;
img {
max-width: 80%;
max-height: 80%;
vertical-align: middle;
display: inline-block;
}
}
.smallIcon {
width: 22px !important;
height: 22px !important;
text-align: center;
}
.legendIconTitle {
cursor: pointer;
flex: 1 1;
}
&:hover {
color: #2f6b98;
}
}
2026-03-31 14:17:30 +08:00
</style>