class App { static #eeui = null; static #geolocation = null; constructor() { this.constructor.init(); } static async init() { while (typeof requireModuleJs !== "function") { await new Promise(resolve => setTimeout(resolve, 500)); } this.#eeui = requireModuleJs("eeui"); this.#geolocation = requireModuleJs("eeui/geolocation"); } static async setVariate(key, value) { while (!this.#eeui) { await new Promise(resolve => setTimeout(resolve, 500)); } await this.#eeui.setVariate(key, value); } static async getLocation() { while (!this.#geolocation) { await new Promise(resolve => setTimeout(resolve, 500)); } return new Promise(resolve => { this.#geolocation.get((res) => { resolve(res); }); }); } static closePage() { try { // 方法1: 如果是在 eeui 环境中 if (this.#eeui) { this.#eeui.closePage(); } // 方法2: 如果是从其他页面打开的,可以关闭当前窗口 window.close(); // 方法3: 如果是在 iOS WKWebView 中 try { window.webkit.messageHandlers.closeWindow.postMessage(null); } catch (e) {} // 方法4: 如果是在 Android WebView 中 try { window.android.closeWindow(); } catch (e) {} // 方法5: 如果以上方法都失败,返回上一页 window.history.back(); } catch (e) { console.error('关闭页面失败', e); } } static isArray(obj) { return typeof (obj) == "object" && Object.prototype.toString.call(obj).toLowerCase() == '[object array]' && typeof obj.length == "number"; } static isJson(obj) { return typeof (obj) == "object" && Object.prototype.toString.call(obj).toLowerCase() == "[object object]" && typeof obj.length == "undefined"; } } class CoordTransform { // 私有静态常量 static #x_PI = 3.14159265358979324 * 3000.0 / 180.0; static #PI = 3.1415926535897932384626; static #a = 6378245.0; static #ee = 0.00669342162296594323; /** * WGS84 转 BD09 * @param {number} lng WGS84 经度 * @param {number} lat WGS84 纬度 * @returns {[number, number]} BD09 坐标 [经度, 纬度] */ static wgs84toBd09(lng, lat) { const gcj = CoordTransform.wgs84ToGcj02(lng, lat); return CoordTransform.gcj02ToBd09(gcj[0], gcj[1]); } /** * WGS84 转 GCJ02 * @private */ static wgs84ToGcj02(lng, lat) { if (CoordTransform.outOfChina(lng, lat)) { return [lng, lat]; } let dlat = CoordTransform.transformLat(lng - 105.0, lat - 35.0); let dlng = CoordTransform.transformLng(lng - 105.0, lat - 35.0); const radLat = lat / 180.0 * CoordTransform.#PI; let magic = Math.sin(radLat); magic = 1 - CoordTransform.#ee * magic * magic; const sqrtMagic = Math.sqrt(magic); dlat = (dlat * 180.0) / ((CoordTransform.#a * (1 - CoordTransform.#ee)) / (magic * sqrtMagic) * CoordTransform.#PI); dlng = (dlng * 180.0) / (CoordTransform.#a / sqrtMagic * Math.cos(radLat) * CoordTransform.#PI); const mgLat = lat + dlat; const mgLng = lng + dlng; return [mgLng, mgLat]; } /** * GCJ02 转 BD09 * @private */ static gcj02ToBd09(lng, lat) { const z = Math.sqrt(lng * lng + lat * lat) + 0.00002 * Math.sin(lat * CoordTransform.#x_PI); const theta = Math.atan2(lat, lng) + 0.000003 * Math.cos(lng * CoordTransform.#x_PI); const bdLng = z * Math.cos(theta) + 0.0065; const bdLat = z * Math.sin(theta) + 0.006; return [bdLng, bdLat]; } /** * 判断坐标是否在中国境内 * @private */ static outOfChina(lng, lat) { return (lng < 72.004 || lng > 137.8347) || (lat < 0.8293 || lat > 55.8271); } /** * 转换纬度 * @private */ static transformLat(lng, lat) { let ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat + 0.1 * lng * lat + 0.2 * Math.sqrt(Math.abs(lng)); ret += (20.0 * Math.sin(6.0 * lng * CoordTransform.#PI) + 20.0 * Math.sin(2.0 * lng * CoordTransform.#PI)) * 2.0 / 3.0; ret += (20.0 * Math.sin(lat * CoordTransform.#PI) + 40.0 * Math.sin(lat / 3.0 * CoordTransform.#PI)) * 2.0 / 3.0; ret += (160.0 * Math.sin(lat / 12.0 * CoordTransform.#PI) + 320 * Math.sin(lat * CoordTransform.#PI / 30.0)) * 2.0 / 3.0; return ret; } /** * 转换经度 * @private */ static transformLng(lng, lat) { let ret = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng + 0.1 * lng * lat + 0.1 * Math.sqrt(Math.abs(lng)); ret += (20.0 * Math.sin(6.0 * lng * CoordTransform.#PI) + 20.0 * Math.sin(2.0 * lng * CoordTransform.#PI)) * 2.0 / 3.0; ret += (20.0 * Math.sin(lng * CoordTransform.#PI) + 40.0 * Math.sin(lng / 3.0 * CoordTransform.#PI)) * 2.0 / 3.0; ret += (150.0 * Math.sin(lng / 12.0 * CoordTransform.#PI) + 300.0 * Math.sin(lng / 30.0 * CoordTransform.#PI)) * 2.0 / 3.0; return ret; } } class Loader { static #num = 0; static show() { this.#num++; this.judge(); } static hide() { setTimeout(() => { this.#num--; this.judge(); }, 100) } static judge() { if (this.#num > 0) { document.querySelector('.loading').classList.add('show'); } else if (this.#num <= 0) { document.querySelector('.loading').classList.remove('show'); } } } class BaiduMapPicker { constructor() { this.map = null; this.marker = null; this.localSearch = null; this.currentPoint = null; this.params = { theme: 'light', // 主题风格,light|dark key: null, // 百度地图 API Key title: null, // 页面标题,如:选择打卡地点 label: null, // 搜索列表标签,如:附近的地点 placeholder: null, // 搜索框占位符,如:搜索附近的地点 point: null, // 初始坐标,如:116.404,39.915 noresult: null, // 无搜索结果提示,如:附近没有找到地点 radius: 300, // 搜索半径,单位:300 zoom: 16, // 地图缩放级别 errtip: null, // 定位失败提示 errclose: false, // 定位失败是否关闭页面 channel: null, // 回传数据通道 selectclose: false, // 选择地点是否关闭页面 }; this.init(); } async init() { // 先初始化参数 this.initParams(); // 如果没有 key,直接返回 if (!this.params.key) { console.error('未提供百度地图 API Key'); return; } try { // 等待地图 JS 加载完成 await this.loadMapScript(); // 初始化地图 this.initMap(); } catch (error) { console.error('加载百度地图失败:', error); } } /** * 初始化参数 */ initParams() { // 获取当前URL的查询参数 const urlParams = new URLSearchParams(window.location.search); // 遍历 params 对象的所有属性 Object.keys(this.params).forEach(key => { // 从 URL 参数中获取值 const value = urlParams.get(key); if (value !== null) { // 根据参数类型进行转换 switch (key) { case 'radius': // 转换为数字 this.params[key] = parseInt(value) || 300; break; case 'point': // 转换为坐标数组 const [lng, lat] = value.replace(/[|-]/, ',').split(',').map(parseFloat); if (lng && lat) { this.params[key] = {lng, lat}; } break; default: // 字符串类型直接赋值 this.params[key] = value; } } }); // 设置主题风格 if (!['dark', 'light'].includes(this.params.theme)) { this.params.theme = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; } document.documentElement.classList.add(`theme-${this.params.theme}`); document.body.style.backgroundColor = "#ffffff"; // 设置标题 if (this.params.title) { document.title = this.params.title; } // 设置搜索框占位符 if (this.params.placeholder) { document.getElementById('search-input').placeholder = this.params.placeholder; } // 设置label if (this.params.label) { document.getElementById('address-label').innerText = this.params.label; } } /** * 初始化地图 */ initMap() { // 初始化地图 this.map = new BMap.Map('map-container'); // 初始化本地搜索,移除地图渲染 this.localSearch = new BMap.LocalSearch(this.map, { renderOptions: { autoViewport: false // 关闭自动视野调整 } }); // 设置地图中心点 if (this.params.point) { const {lng, lat} = this.params.point; this.params.point = new BMap.Point(lng, lat); // 设置地图中心点和缩放级别 this.map.centerAndZoom(this.params.point, this.params.zoom); // 创建圆形区域 const circle = new BMap.Circle(this.params.point, this.params.radius, { fillColor: "#333333", fillOpacity: 0.1, strokeColor: "#333333", strokeWeight: 1, strokeOpacity: 0.3 }); this.map.addOverlay(circle); } // 绑定事件 this.bindEvents(); // 初始化时自动定位 Loader.show(); this.getCurrentLocation().then((point) => { Loader.hide(); if (point === null) { this.locationError(); } document.getElementById('map-location').style.display = 'block'; }); } /** * 绑定事件 */ bindEvents() { // 输入框事件 const searchInput = document.getElementById('search-input'); searchInput.addEventListener('keyup', (e) => { if (e.key === 'Enter') { searchInput.blur(); } }); searchInput.addEventListener('blur', () => { this.searchAddress(); }); // 地图定位点击事件 const mapLocation = document.getElementById('map-location'); mapLocation.addEventListener('click', () => { Loader.show() this.getCurrentLocation().then(() => { Loader.hide() }); }); } /** * 获取当前位置 * @returns {Promise} */ getCurrentLocation() { return new Promise(resolve => { App.getLocation().then(res => { if (App.isJson(res) && res.longitude && res.latitude) { const bd09_coord = CoordTransform.wgs84toBd09(res.longitude, res.latitude); const point = new BMap.Point(bd09_coord[0], bd09_coord[1]); this.updateCurrentPoint(point) resolve(point); } else { console.error('定位失败'); resolve(null); } }) }) } /** * 更新当前位置 * @param point */ updateCurrentPoint(point) { this.currentPoint = point; this.map.centerAndZoom(this.currentPoint, this.params.zoom); this.updateMarker(this.currentPoint); this.searchNearby(); } /** * 更新标记点 * @param point */ updateMarker(point) { if (this.marker) { this.marker.setPosition(point); } else { this.marker = new BMap.Marker(point); this.map.addOverlay(this.marker); } } /** * 搜索地址 */ searchAddress() { const keyword = document.getElementById('search-input').value; this.searchNearby(keyword ? [keyword] : []); } /** * 搜索附近的地点 * @param keywords * @param retryCount */ searchNearby(keywords = [], retryCount = 0) { // 当前位置未获取 if (this.currentPoint === null) { return; } // 清除之前的搜索结果 this.localSearch.clearResults(); // 搜索附近的关键词 if (keywords.length === 0) { keywords = ["写字楼", "公司", "银行", "餐馆", "商场", "超市", "学校", "医院", "公交站", "地铁站"] } // 定义一个随机数,用于判断定时器是否过期 this.searchRandom = Math.random(); // 设置搜索完成回调 Loader.show(); this.localSearch.setSearchCompleteCallback((results) => { Loader.hide(); if (this.localSearch.getStatus() !== BMAP_STATUS_SUCCESS) { // 搜索失败 if (retryCount < 60) { const tmpRand = this.searchRandom; Loader.show(); setTimeout(() => { Loader.hide(); tmpRand === this.searchRandom && this.searchNearby(keywords, ++retryCount); }, 1000) return; } } // 搜索结果 document.getElementById('address-list').style.display = 'block'; const array = []; if (results instanceof Array) { results.some(result => { if (!result) { return false; } for (let i = 0; i < result.getCurrentNumPois(); i++) { const poi = result.getPoi(i); poi.distance = this.params.point ? this.map.getDistance(this.params.point, poi.point) : null; array.push(poi); } }); } this.updatePoiList(array); }); // 执行搜索 this.localSearch.searchNearby(keywords, this.currentPoint, this.params.radius); } /** * 更新搜索结果列表 * @param results */ updatePoiList(results) { const poiList = document.getElementById('poi-list'); poiList.innerHTML = ''; // 如果没有搜索结果 if (results.length === 0 && this.params.noresult) { poiList.innerHTML = '
  • ' + this.params.noresult + '
  • '; return; } // 按距离排序(如果有距离信息) results.sort((a, b) => { if (a.distance && b.distance) { return a.distance - b.distance; } return 0; }); results = results.slice(0, 20); // 创建列表项 results.forEach(poi => { const li = document.createElement('li'); const distanceFormat = poi.distance ? `
    ${this.convertDistance(Math.round(poi.distance))}
    ` : ''; li.innerHTML = `
    ${poi.title}
    ${poi.address || ""}${distanceFormat}
    `; li.addEventListener('click', () => { const point = poi.point; this.updateMarker(point); this.map.setCenter(point); // App.setVariate("location::" + this.params.channel, JSON.stringify(poi)); if (this.params.selectclose) { App.closePage(); } }); poiList.appendChild(li); }); // 列表更新后,重新将当前标记点居中显示 setTimeout(() => { if (this.marker) { this.map.setCenter(this.marker.getPosition()); } }, 100); // 添加小延时确保DOM已更新 } /** * 转换距离显示 * @param d * @returns {string} */ convertDistance(d) { if (d > 1000) { return (d / 1000).toFixed(1) + 'km'; } return d.toFixed(0) + 'm'; } /** * 定位失败提示 */ locationError() { if (this.params.errtip) { alert(this.params.errtip); } if (this.params.errclose) { App.closePage(); } } /** * 加载百度地图脚本 * @returns {Promise} */ loadMapScript() { return new Promise((resolve, reject) => { // 如果已经加载过,直接返回 if (window.BMap) { resolve(); return; } // 创建script标签 const script = document.createElement('script'); script.type = 'text/javascript'; script.src = `https://api.map.baidu.com/api?v=3.0&ak=${this.params.key}&callback=initBaiduMap`; // 添加回调函数 window.initBaiduMap = () => { resolve(); delete window.initBaiduMap; }; // 处理加载错误 script.onerror = () => { reject(new Error('百度地图脚本加载失败')); }; // 添加到页面 document.body.appendChild(script); }); } } // 初始化 document.addEventListener('DOMContentLoaded', () => { new App(); new BaiduMapPicker(); });