| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495 |
- <template>
- <div
- class="light-map"
- :class="{
- 'full': full ,
- 'small': small
- }"
- @click="emit('mapClick')"
- >
- <slot />
- <map
- id="prevMap"
- map-id="prevMap"
- class="light-map-map"
- :style="{
- pointerEvents: small ? 'none' : undefined
- }"
- :show-location="true"
- :enable-poi="!small"
- :scale="12"
- :longitude="lonlat?.longitude"
- :latitude="lonlat?.latitude"
- @markertap="onMarkerTap"
- />
- <!-- 状态显示 -->
- <FlexCol v-if="mapLoader.error.value"
- gap="gap.md"
- position="absolute"
- inset="0"
- center
- >
- <FlexCol center gap="gap.md" padding="padding.md" backgroundColor="white" radius="radius.md">
- <Icon name="warning" size="44" color="red" />
- <Text textAlign="center" :text="mapLoader.error.value" />
- </FlexCol>
- </FlexCol>
- <FlexCol v-else-if="mapLoader.status.value === 'loading'"
- gap="gap.md"
- position="absolute"
- inset="0"
- center
- >
- <ActivityIndicator :size="64" />
- </FlexCol>
- <!-- 导航栏 -->
- <FlexCol
- position="absolute"
- :inset="{ l: 0, b: 0,r: 0 }"
- :zIndex="20"
- :padding="full ? [0, 10] : 20"
- >
- <FlexRow align="center" justify="space-between">
- <view v-if="small"></view>
- <template v-else>
- <BoxMid direction="row" wrap gap="gap.md" align="center" :padding="15">
- <NButton
- :text="city + ' ▼'"
- color="transparent"
- type="custom"
- :radius="30"
- :padding="30"
- :textProps="{
- maxWidth: 150,
- lines: 1
- }"
- @click="emit('changeCity')"
- />
- <SimpleDropDownPicker
- class="light-map-region-picker"
- backgroundColor="transparent"
- :modelValue="selectedRegion"
- @update:modelValue="onSelectedRegion"
- :columns="regionLoader.content.value"
- />
- <SimpleDropDownPicker
- class="light-map-region-picker"
- backgroundColor="transparent"
- v-model:modelValue="lightState"
- :columns="[
- {
- id: 1,
- name: '点亮状态',
- },
- {
- id: 2,
- name: '已点亮',
- },
- {
- id: 3,
- name: '未点亮',
- },
- ]"
- />
-
- <FrameButton
- icon="search"
- size="small"
- @click="showSearch"
- />
- <FrameButton
- icon="navigation"
- size="small"
- @click="emit('getCurrentLonlat')"
- />
- <Width />
- </BoxMid>
- </template>
- </FlexRow>
- <StatusBarSpace v-if="full" />
- </FlexCol>
- <!-- 上下渐变遮罩 -->
- <BackgroundBox
- v-if="full"
- position="fixed"
- :inset="{ l: 0, t: 0, r: 0 }"
- :height="500"
- :zIndex="19"
- color2="transparent"
- color1="white"
- />
- <BackgroundBox
- v-if="full"
- position="fixed"
- :inset="{ l: 0, b: 0, r: 0 }"
- :zIndex="19"
- :height="300"
- color1="transparent"
- color2="white"
- />
- </div>
- </template>
- <script setup lang="ts">
- import { computed, getCurrentInstance, onMounted, ref, watch } from 'vue';
- import { assertNotNull, waitTimeOut } from '@imengyu/imengyu-utils';
- import { toast } from '@/components/dialog/CommonRoot';
- import { useSimpleDataLoader } from '@/components/composeabe/loader/SimpleDataLoader';
- import LightVillageApi, { type VillageMapItem } from '@/api/light/LightVillageApi';
- import AppCofig from '@/common/config/AppCofig';
- import SimpleDropDownPicker from '@/common/components/SimpleDropDownPicker.vue';
- import NButton from '@/components/basic/Button.vue';
- import FlexCol from '@/components/layout/FlexCol.vue';
- import Text from '@/components/basic/Text.vue';
- import Icon from '@/components/basic/Icon.vue';
- import ActivityIndicator from '@/components/basic/ActivityIndicator.vue';
- import Dialog from '@/components/dialog/Dialog.vue';
- import Field from '@/components/form/Field.vue';
- import FlexRow from '@/components/layout/FlexRow.vue';
- import StatusBarSpace from '@/components/layout/space/StatusBarSpace.vue';
- import RegionApi from '@/api/map/RegionApi';
- import type { MapMarker } from '@/types/Map';
- import { hideLoading, loading } from '@/components/utils/DialogAction';
- import { showError } from '@/common/composeabe/ErrorDisplay';
- import FrameButton from '@/common/components/FrameButton.vue';
- import BoxMid from '@/common/components/box/BoxMid.vue';
- import Width from '@/components/layout/space/Width.vue';
- import BackgroundBox from '@/components/display/block/BackgroundBox.vue';
- import { navTo } from '@/components/utils/PageAction';
- const instance = getCurrentInstance();
- const mapCtx = uni.createMapContext('prevMap', instance);
- const selectedRegion = ref<number>();
- const villageData = new Map<number, VillageMapItem>();
- const ready = ref(false);
- const hasResItems = ref(false);
- const emit = defineEmits([
- 'getCurrentLonlat',
- 'update:isLightMode',
- 'update:lonlat',
- 'update:district',
- 'selectVillage',
- 'regionChanged',
- 'lightVillage',
- 'createVillage',
- 'changeCity',
- 'mapClick',
- ]);
- const props = withDefaults(defineProps<{
- lonlat?: { longitude: number, latitude: number } | undefined;
- city?: string;
- cityCode?: number;
- district?: string;
- isLightMode?: boolean;
- small?: boolean;
- full?: boolean;
- }>(), {
- });
- const lightState = ref(1);
- const showLighted = computed(() => lightState.value === 2 || lightState.value === 1);
- const showUnLighted = computed(() => lightState.value === 3 || lightState.value === 1);
- const regionLoader = useSimpleDataLoader(async () => {
- if (!props.cityCode)
- return [];
- return ((await RegionApi.getChildList(props.cityCode))
- .filter(p => p.areaCode !== props.cityCode && p.name !== '市辖区')
- .map(p => ({
- ...p,
- name: p.name,
- id: p.areaCode,
- })));
- }, false);
- const mapLoader = useSimpleDataLoader<MapMarker[]>(async () => {
- mapCtx.removeMarkers({
- markerIds: Array.from(villageData.keys()),
- })
- villageData.clear();
- if (!selectedRegion.value)
- return [];
- await waitTimeOut(100);
- let res = (await LightVillageApi.getMapVillage({
- areaCode: selectedRegion.value
- }));
- //如果小地图则只显示200个
- if (props.small && res.length > 200)
- res.splice(200, res.length - 200);
- //如果为空则尝试获取子级
- hasResItems.value = res.length > 0;
- const list = res
- .filter(p => {
- if (p.isLight && showLighted.value)
- return true;
- if (!p.isLight && showUnLighted.value)
- return true;
- return false;
- })
- .map((p, i) => {
- const id = p.villageId ?? i;
- villageData.set(id, p);
- const maker : MapMarker = {
- id,
- title: p.name,
- longitude: Number(p.longitude),
- latitude: Number(p.latitude),
- width: 30,
- height: 30,
- iconPath: '',
- joinCluster: true,
- callout: {
- display: props.small ? 'BYCLICK' : 'ALWAYS',
- content: p.name,
- color: '#000000',
- fontSize: 12,
- padding: 5,
- bgColor: '#ffffff',
- borderRadius: 5,
- },
- }
- if (p.isLight && p.villageId) {
- if (p.lightValue >= 1) {
- maker.iconPath = `https://mncdn.wenlvti.net/app_static/xiangyuan/images/map/Light3.png`;
- } else if (p.lightValue >= 0.5) {
- maker.iconPath = `https://mncdn.wenlvti.net/app_static/xiangyuan/images/map/Light2.png`;
- } else if (p.lightValue >= 0.25) {
- maker.iconPath = `https://mncdn.wenlvti.net/app_static/xiangyuan/images/map/Light1.png`;
- } else {
- maker.iconPath = `https://mncdn.wenlvti.net/app_static/xiangyuan/images/map/Light1.png`;
- }
- const size = Math.floor(30 +(p.lightValue) * 40);
- maker.width = size;
- maker.height = Math.floor(size * 1.05);
-
- } else {
- maker.width = 25;
- maker.height = 27;
- maker.iconPath = `https://mncdn.wenlvti.net/app_static/xiangyuan/images/map/LightUnLight.png`;
- }
- return maker as MapMarker;
- })
- .filter(p =>
- p.longitude && p.latitude
- && !isNaN(p.longitude) && !isNaN(p.latitude)
- && p.longitude > -180 && p.longitude < 180
- && p.latitude > -90 && p.latitude < 90
- );
- asyncAddMarkers(list);
- if (res.length == 1) {
- mapCtx.moveToLocation({
- latitude: Number(list[0].latitude),
- longitude: Number(list[0].longitude),
- });
- } else if (res.length > 1) {
- setTimeout(() => {
- mapCtx.includePoints({
- points: list.map(p => {
- if (!p.longitude || !p.latitude) {
- p.longitude = AppCofig.defaultLonLat[0];
- p.latitude = AppCofig.defaultLonLat[1];
- }
- return {
- latitude: p.latitude,
- longitude: p.longitude,
- }
- }),
- padding: [20, 20, 20, 20],
- });
- }, 200);
- } else {
- try {
- const currentRegion = regionLoader.content.value?.find(p => p.id == selectedRegion.value);
- if (currentRegion) {
- mapCtx.moveToLocation({
- latitude: Number(currentRegion.latitude),
- longitude: Number(currentRegion.longitude),
- });
- }
- } catch (error) {
- console.error(error);
- }
- }
- ready.value = true;
- return list;
- }, false, false);
- const BATCH_SIZE = 25;
- async function asyncAddMarkers(list: MapMarker[]) {
- if (list.length <= BATCH_SIZE) {
- mapCtx.addMarkers({
- clear: true,
- markers: list,
- });
- return;
- }
- for (let i = 0; i < list.length; i += BATCH_SIZE) {
- const batch = list.slice(i, i + BATCH_SIZE);
- mapCtx.addMarkers({
- clear: i === 0,
- markers: batch,
- });
- if (i + BATCH_SIZE < list.length) {
- await waitTimeOut(300);
- }
- }
- }
- async function onMarkerTap(e: any) {
- try {
- loading();
- const villageMapInfo = villageData.get(e.markerId);
- assertNotNull(villageMapInfo, 'markerId错误');
- if (!villageMapInfo.villageId) {
- emit('createVillage', villageMapInfo);
- return;
- }
- const village = await LightVillageApi.getVillageDetails(villageMapInfo.villageId);
- assertNotNull(village, '获取村社详情失败');
- if (props.isLightMode) {
- emit('update:isLightMode', false);
- emit('lightVillage', village);
- return;
- }
- emit('selectVillage', village);
- } catch (error) {
- showError(error);
- } finally {
- hideLoading();
- }
- }
- function onSelectedRegion(regionId: number) {
- selectedRegion.value = regionId;
- mapLoader.reload();
- emit('update:district', regionLoader.content.value?.find(p => p.id === regionId)?.name);
- emit('regionChanged', regionId);
- }
- function setCurrentRegion(regionAreaCode: number) {
- if (regionAreaCode === selectedRegion.value)
- return;
- selectedRegion.value = regionAreaCode;
- emit('regionChanged', selectedRegion.value);
- mapLoader.reload();
- }
- function loadFirstRegion() {
- //加载第一个地区(排除市辖区)
- let code : number | undefined = undefined;
- if (props.district)
- code = regionLoader.content.value?.find((p) => p.name === props.district)?.areaCode;
- if (!code)
- code = regionLoader.content.value?.find((p) => p.id !== props.cityCode)?.areaCode;
- if (!code)
- code = regionLoader.content.value?.[0]?.areaCode;
- if (code)
- setCurrentRegion(code);
- console.log('加载区域数据', code, regionLoader.content.value, props.district);
- }
- function showSearch() {
- navTo('/pages/home/search/index');
- }
- watch(() => props.cityCode, async () => {
- await regionLoader.reload();
- loadFirstRegion();
- }, { immediate: true });
- watch(showLighted, () => mapLoader.reload());
- watch(showUnLighted, () => mapLoader.reload());
- onMounted(async () => {
- mapCtx.initMarkerCluster({
- enableDefaultStyle: false,
- zoomOnClick: true,
- gridSize: props.small ? 25 : 50,
- });
- mapCtx.on('markerClusterCreate', (e: { clusters: any[] }) => {
- const customClusters = e.clusters.map((cluster) => {
- const { center, clusterId, markerIds } = cluster;
- return {
- ...center,
- width: 0,
- height: 0,
- clusterId,
- label: {
- content: markerIds.length.toString(), // 聚合点的数量
- fontSize: 16,
- color: '#fff',
- width: 30,
- height: 30,
- bgColor: '#8bb346', // 背景颜色
- borderRadius: 25,
- textAlign: 'center',
- anchorX: -10,
- anchorY: -35,
- },
- };
- });
- mapCtx.addMarkers({
- markers: customClusters,
- clear: false,
- });
- });
- });
- defineExpose({
- showSearch,
- reload: async () => {
- await regionLoader.reload();
- loadFirstRegion();
- },
- });
- </script>
- <style lang="scss">
- .light-map {
- position: relative;
- width: 100%;
- height: 600rpx;
- border-radius: 30rpx;
- overflow: hidden;
- border: 1px solid #d45652;
- &.full {
- height: 100vh;
- border-radius: 0;
- .light-map-map {
- height: 100vh;
- }
- }
- &.small {
- height: 400rpx;
- border-radius: 20rpx;
- .light-map-map {
- height: 400rpx;
- }
- }
- .light-map-map {
- width: 100%;
- height: 600rpx;
- }
- .light-map-address {
- position: absolute;
- bottom: 20rpx;
- right: 20rpx;
- z-index: 100;
- }
- }
- </style>
|