useMap.ts 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. import { ref, toRefs } from 'vue';
  2. import AMapLoader from '@amap/amap-jsapi-loader';
  3. import { message } from 'ant-design-vue';
  4. interface EnhancedAMapOptions {
  5. key?: string;
  6. version?: string;
  7. plugins?: string[];
  8. securityJsCode?: string;
  9. }
  10. interface MapInstance {
  11. AMap: any;
  12. map: AMap.Map | null;
  13. }
  14. // 全局单例变量
  15. let globalAMap: any = null;
  16. let globalMap: AMap.Map | null = null;
  17. let isInitialized = false;
  18. let initPromise: Promise<MapInstance> | null = null;
  19. const currentObj = ref({
  20. currentAdcode: '',
  21. currentAddress: '',
  22. });
  23. const lnglat = ref<number[]>([]);
  24. export function useAMapEnhanced(options: EnhancedAMapOptions = {}) {
  25. const isLoading = ref(false);
  26. const error = ref<string | null>(null);
  27. // 默认配置
  28. const defaultOptions: EnhancedAMapOptions = {
  29. key: import.meta.env.VITE_MAPKEY || '',
  30. version: '2.0',
  31. plugins: ['AMap.PlaceSearch', 'AMap.Geolocation'],
  32. securityJsCode: 'cbbb175f5c77d9ddf74c9b7d37f52f1c',
  33. };
  34. // 合并配置
  35. const mergedOptions = { ...defaultOptions, ...options };
  36. // 设置安全密钥
  37. if (mergedOptions.securityJsCode) {
  38. window._AMapSecurityConfig = {
  39. securityJsCode: mergedOptions.securityJsCode,
  40. };
  41. }
  42. /**
  43. * 初始化地图(全局单例)
  44. * @param container 地图容器ID或元素
  45. * @param mapOptions 地图配置选项
  46. */
  47. const initMap = async (container: string | HTMLElement, mapOptions: AMap.MapOptions = {}): Promise<MapInstance> => {
  48. // 如果已经在初始化中,则返回该Promise
  49. if (initPromise) {
  50. await initPromise;
  51. return {
  52. AMap: globalAMap,
  53. map: globalMap,
  54. };
  55. }
  56. // 如果已经初始化完成,直接返回
  57. if (isInitialized) {
  58. return {
  59. AMap: globalAMap,
  60. map: globalMap,
  61. };
  62. }
  63. // 检查是否提供了key
  64. if (!mergedOptions.key) {
  65. error.value = '缺少高德地图key';
  66. throw new Error('Missing AMap key');
  67. }
  68. isLoading.value = true;
  69. error.value = null;
  70. // 创建初始化Promise
  71. initPromise = new Promise(async (resolve, reject) => {
  72. try {
  73. // 加载AMap API
  74. const loadedAMap = await AMapLoader.load({
  75. key: mergedOptions.key!,
  76. version: mergedOptions.version || '2.0',
  77. plugins: mergedOptions.plugins,
  78. });
  79. globalAMap = loadedAMap;
  80. // 默认地图配置
  81. const defaultMapOptions: AMap.MapOptions = {
  82. zoom: 11, // 默认缩放级别
  83. ...mapOptions,
  84. };
  85. // 创建地图实例
  86. globalMap = new globalAMap.Map(container, defaultMapOptions);
  87. isInitialized = true;
  88. resolve({
  89. AMap: globalAMap,
  90. map: globalMap,
  91. });
  92. } catch (err: any) {
  93. error.value = err.message || '地图加载失败';
  94. reject(err);
  95. } finally {
  96. isLoading.value = false;
  97. initPromise = null;
  98. }
  99. });
  100. return initPromise;
  101. };
  102. /**
  103. * 搜索POI并定位到结果
  104. * @param keyword 搜索关键词
  105. */
  106. const searchAndLocate = async (keyword: string): Promise<void> => {
  107. if (!globalAMap || !globalMap) {
  108. throw new Error('地图未初始化');
  109. }
  110. return new Promise((resolve, reject) => {
  111. globalAMap.plugin(['AMap.PlaceSearch'], () => {
  112. const placeSearch = new globalAMap.PlaceSearch({
  113. pageSize: 10,
  114. pageIndex: 1,
  115. autoFitView: true,
  116. });
  117. placeSearch.search(keyword, (status: string, result: any) => {
  118. if (status === 'complete' && result.poiList && result.poiList.pois.length > 0) {
  119. const firstPoi = result.poiList.pois[0];
  120. if (firstPoi.location) {
  121. globalMap?.setCenter(firstPoi.location);
  122. globalMap?.setZoom(18);
  123. // // 添加标记点
  124. // const marker = new globalAMap.Marker({
  125. // position: firstPoi.location,
  126. // title: firstPoi.name,
  127. // });
  128. // globalMap?.add(marker);
  129. }
  130. resolve(firstPoi);
  131. } else {
  132. reject(new Error('未找到相关地点'));
  133. }
  134. });
  135. });
  136. });
  137. };
  138. /**
  139. * 根据城市码定位地图
  140. * @param cityCode 城市编码
  141. */
  142. const setCityByCode = async (cityCode: string): Promise<void> => {
  143. if (!globalAMap || !globalMap) {
  144. throw new Error('地图未初始化');
  145. }
  146. return new Promise((resolve, reject) => {
  147. globalMap?.setCity(cityCode, (result) => {
  148. if (result) {
  149. resolve(result);
  150. } else {
  151. reject(new Error('设置城市失败'));
  152. }
  153. });
  154. });
  155. };
  156. /**
  157. * 获取当前位置
  158. */
  159. const getCurrentPosition = async (): Promise<any> => {
  160. if (!globalAMap) {
  161. throw new Error('AMap未加载');
  162. }
  163. return new Promise((resolve, reject) => {
  164. globalAMap.plugin(['AMap.Geolocation'], () => {
  165. const geolocation = new globalAMap.Geolocation({
  166. enableHighAccuracy: true,
  167. timeout: 10000,
  168. });
  169. geolocation.getCurrentPosition((status: string, result: any) => {
  170. if (status === 'complete') {
  171. // 定位到当前位置
  172. getAddressByLngLat(result.position.lng, result.position.lat);
  173. if (result.position) {
  174. globalMap?.setCenter(result.position);
  175. globalMap?.setZoom(18);
  176. }
  177. resolve(result);
  178. } else {
  179. reject(result);
  180. }
  181. });
  182. });
  183. });
  184. };
  185. /**
  186. * 通过经纬度获取省市区信息
  187. * @param longitude 经度
  188. * @param latitude 纬度
  189. * @returns 省市区信息
  190. */
  191. const getAddressByLngLat = async (longitude: number, latitude: number): Promise<any> => {
  192. if (!globalAMap) {
  193. throw new Error('AMap未加载');
  194. }
  195. return new Promise((resolve, reject) => {
  196. globalAMap.plugin(['AMap.Geocoder'], () => {
  197. const geocoder = new globalAMap.Geocoder({
  198. city: '全国',
  199. radius: 1000,
  200. extensions: 'all',
  201. });
  202. lnglat.value = [longitude, latitude];
  203. geocoder.getAddress(lnglat.value, (status: string, result: any) => {
  204. if (status === 'complete' && result.regeocode) {
  205. currentObj.value.currentAddress = result.regeocode.formattedAddress;
  206. if (result.regeocode.addressComponent) {
  207. currentObj.value.currentAdcode = result.regeocode.addressComponent.adcode;
  208. }
  209. resolve(result.regeocode);
  210. } else {
  211. reject(new Error('无法获取地址信息'));
  212. }
  213. });
  214. });
  215. });
  216. };
  217. /**
  218. * 根据城市码和详细地址精确定位
  219. * @param cityCode 城市编码
  220. * @param address 详细地址
  221. */
  222. const locateByCityAndAddress = async (cityCode: string, address: string): Promise<any> => {
  223. if (!globalAMap || !globalMap) {
  224. throw new Error('地图未初始化');
  225. }
  226. // 先根据城市码设置地图城市
  227. await setCityByCode(cityCode);
  228. // 然后在指定城市内搜索详细地址
  229. return new Promise((resolve, reject) => {
  230. globalAMap.plugin(['AMap.PlaceSearch'], () => {
  231. const placeSearch = new globalAMap.PlaceSearch({
  232. city: cityCode,
  233. citylimit: true,
  234. pageSize: 10,
  235. pageIndex: 1,
  236. autoFitView: true,
  237. });
  238. placeSearch.search(address, (status: string, result: any) => {
  239. if (status === 'complete' && result.poiList && result.poiList.pois.length > 0) {
  240. const firstPoi = result.poiList.pois[0];
  241. if (firstPoi.location) {
  242. globalMap?.setCenter(firstPoi.location);
  243. globalMap?.setZoom(18);
  244. }
  245. resolve(firstPoi);
  246. } else {
  247. reject(new Error('未找到相关地点'));
  248. message.error('该区域下暂无该地点');
  249. }
  250. });
  251. });
  252. });
  253. };
  254. /**
  255. * 销毁地图
  256. */
  257. const destroyMap = () => {
  258. if (globalMap) {
  259. globalMap.destroy();
  260. globalMap = null;
  261. }
  262. isInitialized = false;
  263. initPromise = null;
  264. currentObj.value.currentAdcode = ''; // 清空adcode数据
  265. currentObj.value.currentAddress = '';
  266. lnglat.value = [];
  267. };
  268. return {
  269. // 响应式数据
  270. isLoading,
  271. error,
  272. currentObj,
  273. lnglat,
  274. // 方法
  275. initMap,
  276. searchAndLocate,
  277. getCurrentPosition,
  278. destroyMap,
  279. setCityByCode,
  280. locateByCityAndAddress,
  281. getAddressByLngLat,
  282. // 提供获取全局地图实例的方法
  283. getGlobalMap: () => globalMap,
  284. getGlobalAMap: () => globalAMap,
  285. isMapInitialized: () => isInitialized,
  286. };
  287. }