LightMap.vue 8.1 KB

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