LightMap.vue 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  1. <template>
  2. <div
  3. class="light-map"
  4. :class="{
  5. 'full': full ,
  6. 'small': small
  7. }"
  8. >
  9. <slot />
  10. <map
  11. id="prevMap"
  12. map-id="prevMap"
  13. class="light-map-map"
  14. :enable-poi="false"
  15. :scale="12"
  16. :longitude="lonlat?.longitude"
  17. :latitude="lonlat?.latitude"
  18. @markertap="onMarkerTap"
  19. />
  20. <FlexCol v-if="isEmptyRegion"
  21. gap="gap.xl"
  22. position="absolute"
  23. inset="0"
  24. padding="space.xl"
  25. center
  26. backgroundColor="mask.default"
  27. >
  28. <Text fontConfig="importantTitle" textAlign="center">您选择的地区还未开通亮乡源数据,可联系客服开通</Text>
  29. <button open-type="contact" class="remove-button-style">
  30. <FlexCol padding="space.md" radius="radius.lg" center backgroundColor="white">
  31. <Text fontConfig="primaryTitle">联系客服</Text>
  32. </FlexCol>
  33. </button>
  34. </FlexCol>
  35. <SimpleDropDownPicker
  36. v-if="!isEmptyRegion"
  37. class="light-map-region-picker"
  38. :modelValue="selectedRegion"
  39. @update:modelValue="onSelectedRegion"
  40. :columns="regionLoader.content.value"
  41. />
  42. <FlexCol
  43. v-if="isDev"
  44. :innerStyle="{
  45. position: 'absolute',
  46. bottom: '20rpx',
  47. left: '20rpx',
  48. zIndex: 100,
  49. backgroundColor: '#ffffff',
  50. }"
  51. >
  52. <Text :text="`lonlat:`" />
  53. <Text :text="`${lonlat?.longitude}`" />
  54. <Text :text="`${lonlat?.latitude}`" />
  55. </FlexCol>
  56. <NButton
  57. :innerStyle="{
  58. position: 'absolute',
  59. bottom: '20rpx',
  60. right: '20rpx',
  61. zIndex: 100,
  62. backgroundColor: '#ffffff',
  63. }"
  64. text="定位"
  65. icon="navigation"
  66. @click="emit('getCurrentLonlat')"
  67. />
  68. </div>
  69. </template>
  70. <script setup lang="ts">
  71. import { computed, getCurrentInstance, onMounted, ref, watch } from 'vue';
  72. import { navTo } from '@/components/utils/PageAction';
  73. import { waitTimeOut } from '@imengyu/imengyu-utils';
  74. import { useSimpleDataLoader } from '@/components/composeabe/loader/SimpleDataLoader';
  75. import LightVillageApi, { VillageListItem } from '@/api/light/LightVillageApi';
  76. import type { MapMarker } from '@/types/Map';
  77. import AppCofig, { isDev } from '@/common/config/AppCofig';
  78. import SimpleDropDownPicker from '@/common/components/SimpleDropDownPicker.vue';
  79. import NButton from '@/components/basic/Button.vue';
  80. import FlexCol from '@/components/layout/FlexCol.vue';
  81. import Text from '@/components/basic/Text.vue';
  82. import CommonContent from '@/api/CommonContent';
  83. const instance = getCurrentInstance();
  84. const mapCtx = uni.createMapContext('prevMap', instance);
  85. const selectedRegion = ref<number>();
  86. const nextNeedAutoFocus = ref(false);
  87. const villageData = new Map<number, VillageListItem>();
  88. const emit = defineEmits([
  89. 'getCurrentLonlat',
  90. 'update:isLightMode',
  91. 'update:lonlat',
  92. 'selectVillage',
  93. 'regionChanged',
  94. ]);
  95. const props = defineProps<{
  96. lonlat?: { longitude: number, latitude: number } | undefined;
  97. city?: string;
  98. isLightMode?: boolean;
  99. small?: boolean;
  100. full?: boolean;
  101. }>();
  102. const regionLoader = useSimpleDataLoader(async () => {
  103. if (!props.city)
  104. return [];
  105. return (await CommonContent.searchRegion(props.city)).map(p => ({
  106. id: p.id,
  107. name: p.title,
  108. }));
  109. }, false);
  110. const mapLoader = useSimpleDataLoader<MapMarker[]>(async () => {
  111. mapCtx.removeMarkers({
  112. markerIds: Array.from(villageData.keys()),
  113. })
  114. villageData.clear();
  115. if (!selectedRegion.value)
  116. return [];
  117. await waitTimeOut(200);
  118. const res = (await LightVillageApi.getVillageList(undefined, selectedRegion.value, undefined, 1, 100)).map((p, i) => {
  119. villageData.set(p.id, p);
  120. const maker : MapMarker = {
  121. id: p.id ?? i,
  122. title: p.name,
  123. longitude: Number(p.longitude),
  124. latitude: Number(p.latitude),
  125. width: 30,
  126. height: 30,
  127. iconPath: '',
  128. joinCluster: true,
  129. callout: {
  130. display: 'ALWAYS',
  131. content: p.name,
  132. color: '#000000',
  133. fontSize: 12,
  134. padding: 5,
  135. bgColor: '#ffffff',
  136. borderRadius: 5,
  137. },
  138. }
  139. if (p.isLight) {
  140. if (p.lightValue >= 1) {
  141. maker.iconPath = `https://mncdn.wenlvti.net/app_static/xiangyuan/images/map/Light3.png`;
  142. } else if (p.lightValue >= 0.5) {
  143. maker.iconPath = `https://mncdn.wenlvti.net/app_static/xiangyuan/images/map/Light2.png`;
  144. } else if (p.lightValue >= 0.25) {
  145. maker.iconPath = `https://mncdn.wenlvti.net/app_static/xiangyuan/images/map/Light1.png`;
  146. } else {
  147. maker.iconPath = `https://mncdn.wenlvti.net/app_static/xiangyuan/images/map/Light1.png`;
  148. }
  149. const size = Math.floor(35 +(p.lightValue) * 10);
  150. maker.width = size;
  151. maker.height = size;
  152. } else {
  153. maker.width = 35;
  154. maker.height = 35;
  155. maker.iconPath = `https://mncdn.wenlvti.net/app_static/xiangyuan/images/map/LightUnLight.png`;
  156. }
  157. return maker as MapMarker;
  158. });
  159. mapCtx.addMarkers({
  160. clear: true,
  161. markers: res,
  162. })
  163. if (nextNeedAutoFocus.value) {
  164. setTimeout(() => {
  165. mapCtx.includePoints({
  166. points: res.map(p => {
  167. if (!p.longitude || !p.latitude) {
  168. p.longitude = AppCofig.defaultLonLat[0];
  169. p.latitude = AppCofig.defaultLonLat[1];
  170. }
  171. return {
  172. latitude: p.latitude,
  173. longitude: p.longitude,
  174. }
  175. }),
  176. padding: [20, 20, 20, 20],
  177. });
  178. }, 200);
  179. }
  180. return res;
  181. }, false, false, true);
  182. const isEmptyRegion = computed(() => {
  183. return !selectedRegion.value || !mapLoader.content.value?.length;
  184. });
  185. function onMarkerTap(e: any) {
  186. if (props.isLightMode) {
  187. emit('update:isLightMode', false);
  188. navTo('/pages/home/light/submit', {
  189. villageId: e.markerId,
  190. });
  191. return;
  192. }
  193. const village = villageData.get(e.markerId);
  194. if (village) {
  195. emit('selectVillage', village);
  196. }
  197. }
  198. function onSelectedRegion(regionId: number) {
  199. selectedRegion.value = regionId;
  200. nextNeedAutoFocus.value = true;
  201. mapLoader.reload();
  202. emit('regionChanged', regionId);
  203. }
  204. function setCurrentRegion(regionName: string) {
  205. selectedRegion.value = regionLoader.content.value?.find(p => p.name == regionName)?.id ||
  206. regionLoader.content.value?.[0]?.id || undefined;
  207. emit('regionChanged', selectedRegion.value);
  208. mapLoader.reload();
  209. }
  210. watch(() => props.city, async (newVal) => {
  211. await regionLoader.reload();
  212. setCurrentRegion(newVal || '');
  213. await mapLoader.reload();
  214. }, { immediate: true });
  215. onMounted(async () => {
  216. mapCtx.initMarkerCluster({
  217. enableDefaultStyle: false,
  218. zoomOnClick: true,
  219. gridSize: 40,
  220. });
  221. mapCtx.on('markerClusterCreate', (e: { clusters: any[] }) => {
  222. const customClusters = e.clusters.map((cluster) => {
  223. const { center, clusterId, markerIds } = cluster;
  224. return {
  225. ...center,
  226. width: 0,
  227. height: 0,
  228. clusterId,
  229. label: {
  230. content: markerIds.length.toString(), // 聚合点的数量
  231. fontSize: 16,
  232. color: '#fff',
  233. width: 30,
  234. height: 30,
  235. bgColor: '#8bb346', // 背景颜色
  236. borderRadius: 25,
  237. textAlign: 'center',
  238. anchorX: -10,
  239. anchorY: -35,
  240. },
  241. };
  242. });
  243. mapCtx.addMarkers({
  244. markers: customClusters,
  245. clear: false,
  246. });
  247. });
  248. await regionLoader.reload();
  249. });
  250. </script>
  251. <style lang="scss">
  252. .light-map {
  253. position: relative;
  254. width: 100%;
  255. height: 600rpx;
  256. border-radius: 30rpx;
  257. overflow: hidden;
  258. &.full {
  259. height: 100vh;
  260. border-radius: 0;
  261. .light-map-map {
  262. height: 100vh;
  263. }
  264. }
  265. &.small {
  266. height: 500rpx;
  267. border-radius: 20rpx;
  268. .light-map-map {
  269. height: 500rpx;
  270. }
  271. }
  272. .light-map-map {
  273. width: 100%;
  274. height: 600rpx;
  275. }
  276. .light-map-region-picker {
  277. position: absolute;
  278. bottom: 20rpx;
  279. left: 50%;
  280. transform: translateX(-50%);
  281. z-index: 100;
  282. }
  283. .light-map-address {
  284. position: absolute;
  285. bottom: 20rpx;
  286. right: 20rpx;
  287. z-index: 100;
  288. }
  289. }
  290. </style>