diff --git a/common/config.js b/common/config.js
index 5801610..80f6f94 100644
--- a/common/config.js
+++ b/common/config.js
@@ -20,4 +20,7 @@ switch (mode) {
// 微信配置
const WECHAT_APPID = 'wx3ff67f2e2b0c62ca' // 从manifest.json中获取的appid
-export { ROOTPATH, WECHAT_APPID }
+// 天地图(H5 首页地图)
+const TIANDITU_TK = '0c86c1c69a09ecaa5e9b3d0373fb67bf'
+
+export { ROOTPATH, WECHAT_APPID, TIANDITU_TK }
diff --git a/common/coord.js b/common/coord.js
new file mode 100644
index 0000000..7bae019
--- /dev/null
+++ b/common/coord.js
@@ -0,0 +1,68 @@
+/**
+ * 坐标转换:业务数据为腾讯/国测局 GCJ-02,天地图底图为 WGS84/CGCS2000
+ */
+const PI = Math.PI
+const A = 6378245.0
+const EE = 0.00669342162296594323
+
+function outOfChina(lng, lat) {
+ return lng < 72.004 || lng > 137.8347 || lat < 0.8293 || lat > 55.8271
+}
+
+function 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 * PI) + 20.0 * Math.sin(2.0 * lng * PI)) * 2.0 / 3.0
+ ret += (20.0 * Math.sin(lat * PI) + 40.0 * Math.sin(lat / 3.0 * PI)) * 2.0 / 3.0
+ ret += (160.0 * Math.sin(lat / 12.0 * PI) + 320 * Math.sin(lat * PI / 30.0)) * 2.0 / 3.0
+ return ret
+}
+
+function 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 * PI) + 20.0 * Math.sin(2.0 * lng * PI)) * 2.0 / 3.0
+ ret += (20.0 * Math.sin(lng * PI) + 40.0 * Math.sin(lng / 3.0 * PI)) * 2.0 / 3.0
+ ret += (150.0 * Math.sin(lng / 12.0 * PI) + 300.0 * Math.sin(lng / 30.0 * PI)) * 2.0 / 3.0
+ return ret
+}
+
+/** GCJ-02 → WGS84(天地图展示用) */
+export function gcj02ToWgs84(lng, lat) {
+ lng = parseFloat(lng)
+ lat = parseFloat(lat)
+ if (isNaN(lng) || isNaN(lat)) return { lng, lat }
+ if (outOfChina(lng, lat)) return { lng, lat }
+ let dlat = transformLat(lng - 105.0, lat - 35.0)
+ let dlng = transformLng(lng - 105.0, lat - 35.0)
+ const radlat = lat / 180.0 * PI
+ let magic = Math.sin(radlat)
+ magic = 1 - EE * magic * magic
+ const sqrtmagic = Math.sqrt(magic)
+ dlat = (dlat * 180.0) / ((A * (1 - EE)) / (magic * sqrtmagic) * PI)
+ dlng = (dlng * 180.0) / (A / sqrtmagic * Math.cos(radlat) * PI)
+ const mglat = lat + dlat
+ const mglng = lng + dlng
+ return { lng: lng * 2 - mglng, lat: lat * 2 - mglat }
+}
+
+/** WGS84 → GCJ-02 */
+export function wgs84ToGcj02(lng, lat) {
+ lng = parseFloat(lng)
+ lat = parseFloat(lat)
+ if (isNaN(lng) || isNaN(lat)) return { lng, lat }
+ if (outOfChina(lng, lat)) return { lng, lat }
+ let dlat = transformLat(lng - 105.0, lat - 35.0)
+ let dlng = transformLng(lng - 105.0, lat - 35.0)
+ const radlat = lat / 180.0 * PI
+ let magic = Math.sin(radlat)
+ magic = 1 - EE * magic * magic
+ const sqrtmagic = Math.sqrt(magic)
+ dlat = (dlat * 180.0) / ((A * (1 - EE)) / (magic * sqrtmagic) * PI)
+ dlng = (dlng * 180.0) / (A / sqrtmagic * Math.cos(radlat) * PI)
+ return { lng: lng + dlng, lat: lat + dlat }
+}
+
+/** uni-app scale(约3-20) 转天地图 zoom(约1-18) */
+export function uniScaleToTdtZoom(scale) {
+ const s = parseInt(scale, 10) || 11
+ return Math.max(3, Math.min(18, s + 1))
+}
diff --git a/common/h5-asset.js b/common/h5-asset.js
new file mode 100644
index 0000000..ffeb770
--- /dev/null
+++ b/common/h5-asset.js
@@ -0,0 +1,47 @@
+/**
+ * H5 静态资源转绝对 URL(兼容 manifest publicPath: /h5walksz/)
+ * 注意:天地图 T.Icon 不支持 data:/blob:,标注图必须使用 http(s) 地址
+ */
+export function resolveH5AssetUrl(path) {
+ if (!path || typeof path !== 'string') return path
+ // data/blob 不能作为网络图片加载,交给 normalizeMarkerIconUrl 处理
+ if (/^(https?:)/i.test(path)) return path
+
+ let assetPath = path
+ if (!assetPath.startsWith('/')) {
+ assetPath = '/' + assetPath
+ }
+
+ if (typeof window === 'undefined') {
+ const base = (typeof process !== 'undefined' && process.env && process.env.BASE_URL) || '/h5walksz/'
+ return base.replace(/\/$/, '') + assetPath
+ }
+
+ let basePath = ''
+ if (typeof process !== 'undefined' && process.env && process.env.BASE_URL) {
+ basePath = process.env.BASE_URL.replace(/\/$/, '')
+ }
+ if (!basePath) {
+ const match = window.location.pathname.match(/^\/(h5walksz)(?:\/|$)/)
+ basePath = match ? '/' + match[1] : ''
+ }
+ return window.location.origin + basePath + assetPath
+}
+
+/** 默认地图标注图(固定 http 路径,避免 webpack 内联为 data:) */
+export function getDefaultMarkerUrl() {
+ return resolveH5AssetUrl('/static/home-marker.png')
+}
+
+/**
+ * 标注图标 URL 规范化(天地图仅支持 http/https)
+ */
+export function normalizeMarkerIconUrl(url) {
+ if (!url || /^data:|^blob:/i.test(url)) {
+ return getDefaultMarkerUrl()
+ }
+ if (/^https?:\/\//i.test(url)) {
+ return url
+ }
+ return resolveH5AssetUrl(url)
+}
diff --git a/common/tianditu-h5.js b/common/tianditu-h5.js
new file mode 100644
index 0000000..e0c2c21
--- /dev/null
+++ b/common/tianditu-h5.js
@@ -0,0 +1,176 @@
+/**
+ * 天地图 H5 封装(仅浏览器端)
+ */
+import { getDefaultMarkerUrl, normalizeMarkerIconUrl } from '@/common/h5-asset.js'
+
+const API_BASE = 'https://api.tianditu.gov.cn/api?v=4.0'
+
+let scriptPromise = null
+
+export function loadTiandituScript(tk) {
+ if (typeof window === 'undefined') {
+ return Promise.reject(new Error('非浏览器环境'))
+ }
+ if (window.T && window.T.Map) {
+ return Promise.resolve(window.T)
+ }
+ if (scriptPromise) return scriptPromise
+ scriptPromise = new Promise((resolve, reject) => {
+ const existed = document.querySelector('script[data-tianditu-api]')
+ if (existed) {
+ existed.addEventListener('load', () => resolve(window.T))
+ existed.addEventListener('error', reject)
+ return
+ }
+ const script = document.createElement('script')
+ script.type = 'text/javascript'
+ script.src = `${API_BASE}&tk=${tk}`
+ script.setAttribute('data-tianditu-api', '1')
+ script.onload = () => {
+ if (window.T) resolve(window.T)
+ else reject(new Error('天地图 API 加载失败'))
+ }
+ script.onerror = () => reject(new Error('天地图脚本加载失败'))
+ document.head.appendChild(script)
+ })
+ return scriptPromise
+}
+
+export class TiandituMapHelper {
+ constructor(options) {
+ this.containerId = options.containerId
+ this.tk = options.tk
+ this.onMarkerClick = options.onMarkerClick || (() => {})
+ this.map = null
+ this.pointMarkers = []
+ this.userMarker = null
+ this._defaultIconUrl = getDefaultMarkerUrl()
+ }
+
+ async init(lng, lat, zoom) {
+ await loadTiandituScript(this.tk)
+ await new Promise((r) => {
+ if (typeof requestAnimationFrame === 'function') requestAnimationFrame(r)
+ else setTimeout(r, 50)
+ })
+ const el = document.getElementById(this.containerId)
+ if (!el) throw new Error('地图容器不存在: ' + this.containerId)
+ const T = window.T
+ this.map = new T.Map(this.containerId)
+ this.map.centerAndZoom(new T.LngLat(lng, lat), zoom)
+ if (typeof this.map.enableScrollWheelZoom === 'function') {
+ this.map.enableScrollWheelZoom()
+ }
+ await new Promise((r) => setTimeout(r, 200))
+ if (typeof this.map.checkResize === 'function') {
+ this.map.checkResize()
+ }
+ return this.map
+ }
+
+ clearPointMarkers() {
+ if (!this.map) return
+ this.pointMarkers.forEach((m) => {
+ try {
+ this.map.removeOverLay(m)
+ } catch (e) {}
+ })
+ this.pointMarkers = []
+ }
+
+ resolveIconUrl(rawUrl) {
+ const iconUrl = normalizeMarkerIconUrl(rawUrl || this._defaultIconUrl)
+ if (!/^https?:\/\//i.test(iconUrl)) {
+ return this._defaultIconUrl
+ }
+ return iconUrl
+ }
+
+ async setPointMarkers(list) {
+ if (!this.map || !window.T) return
+ const T = window.T
+ this.clearPointMarkers()
+ if (!list || !list.length) return
+
+ for (let i = 0; i < list.length; i++) {
+ const item = list[i]
+ const lng = parseFloat(item.lng)
+ const lat = parseFloat(item.lat)
+ if (isNaN(lng) || isNaN(lat)) continue
+
+ const iconUrl = this.resolveIconUrl(item.iconUrl)
+ const point = new T.LngLat(lng, lat)
+ const MARKER_SIZE = 44
+ const icon = new T.Icon({
+ iconUrl,
+ iconSize: new T.Point(MARKER_SIZE, MARKER_SIZE),
+ iconAnchor: new T.Point(MARKER_SIZE / 2, MARKER_SIZE)
+ })
+ const marker = new T.Marker(point, { icon })
+ if (typeof marker.setZIndexOffset === 'function') {
+ marker.setZIndexOffset(1000)
+ }
+ const pointId = item.id
+ marker.addEventListener('click', () => {
+ this.onMarkerClick({ markerId: pointId })
+ })
+ this.map.addOverLay(marker)
+ this.pointMarkers.push(marker)
+ }
+
+ if (typeof this.map.checkResize === 'function') {
+ setTimeout(() => this.map.checkResize(), 150)
+ }
+ }
+
+ setUserMarker(lng, lat) {
+ if (!this.map || lng == null || lat == null) return
+ const T = window.T
+ const point = new T.LngLat(lng, lat)
+ if (this.userMarker) {
+ try {
+ this.map.removeOverLay(this.userMarker)
+ } catch (e) {}
+ this.userMarker = null
+ }
+ this.userMarker = new T.Circle(point, 60, {
+ color: '#1791fc',
+ weight: 2,
+ opacity: 0.9,
+ fillColor: '#1791fc',
+ fillOpacity: 0.25,
+ lineStyle: 'solid'
+ })
+ this.map.addOverLay(this.userMarker)
+ }
+
+ centerAndZoom(lng, lat, zoom) {
+ if (!this.map) return
+ this.map.centerAndZoom(new T.LngLat(lng, lat), zoom)
+ }
+
+ panTo(lng, lat) {
+ if (!this.map) return
+ this.map.panTo(new T.LngLat(lng, lat))
+ }
+
+ getZoom() {
+ return this.map ? this.map.getZoom() : null
+ }
+
+ destroy() {
+ this.clearPointMarkers()
+ if (this.userMarker && this.map) {
+ try {
+ this.map.removeOverLay(this.userMarker)
+ } catch (e) {}
+ }
+ this.userMarker = null
+ this.map = null
+ }
+}
+
+export function buildTiandituNavUrl(wgsLng, wgsLat, name) {
+ const n = encodeURIComponent(name || '目的地')
+ return `https://map.tianditu.gov.cn/?l=${wgsLng}&lat=${wgsLat}&name=${n}`
+}
diff --git a/common/util.js b/common/util.js
index dc9a9b4..cfeaa73 100644
--- a/common/util.js
+++ b/common/util.js
@@ -4,6 +4,11 @@
*/
import moment from 'moment';
import { lang } from 'moment';
+// #ifdef H5
+import { isWechatBrowser, openWechatLocation } from '@/common/wechat-jssdk.js'
+import { gcj02ToWgs84 } from '@/common/coord.js'
+import { buildTiandituNavUrl } from '@/common/tianditu-h5.js'
+// #endif
const base64ToFile = (dataurl, filename = 'file') => {
let arr = dataurl.split(',')
@@ -163,17 +168,19 @@ const toMapAPP = (lat,lng,name) => {
})
// #endif
// #ifdef H5
- // H5端使用腾讯地图URL导航
const lat1 = parseFloat(lat)
const lng1 = parseFloat(lng)
- const nameEncoded = encodeURIComponent(name || '目的地')
- // 使用腾讯地图路线规划URL
- const url = `https://apis.map.qq.com/uri/v1/routeplan?type=drive&to=${lat1},${lng1}&tocoord=gcj02&toname=${nameEncoded}&referer=myapp`
- // 在新窗口打开
+ if (isWechatBrowser()) {
+ openWechatLocation(lat1, lng1, name).catch(() => {
+ toast('打开导航失败,请稍后重试')
+ })
+ return
+ }
+ const wgs = gcj02ToWgs84(lng1, lat1)
+ const url = buildTiandituNavUrl(wgs.lng, wgs.lat, name)
if (typeof window !== 'undefined') {
window.open(url, '_blank')
} else {
- // 如果window不存在,使用uni API
plus && plus.runtime.openURL(url)
}
// #endif
diff --git a/common/wechat-jssdk.js b/common/wechat-jssdk.js
index 1906702..68a806a 100644
--- a/common/wechat-jssdk.js
+++ b/common/wechat-jssdk.js
@@ -1,134 +1,293 @@
/*
- * 微信JS-SDK工具
- * 用于H5端的微信分享功能
+ * 微信公众号 H5 - 微信 JS-SDK
+ * 签名接口 /wechat-share 需要 Bearer token,须在登录成功后调用
*/
import { ROOTPATH, WECHAT_APPID } from '@/common/config.js'
-let wx = null
+const DEFAULT_JS_API = [
+ 'getLocation',
+ 'openLocation',
+ 'updateAppMessageShareData',
+ 'updateTimelineShareData'
+]
+
+const SIGNATURE_APIS = [
+ '/api/mobile/user/wechat-share',
+ '/api/mobile/user/wechat-login-url'
+]
+
+let wxInstance = null
+let wxInitPromise = null
+
+export function isWechatBrowser() {
+ return typeof navigator !== 'undefined' && /MicroMessenger/i.test(navigator.userAgent)
+}
+
+export function getStoredToken() {
+ const walksz_lifeData = uni.getStorageSync('walksz_lifeData') || {}
+ return walksz_lifeData.vuex_token || ''
+}
+
+export function resetWxJssdk() {
+ wxInitPromise = null
+}
+
+/**
+ * 参与签名的页面 URL(须与后端 wechat-share 入参 url 完全一致)
+ * 后端常按 https://域名/h5walksz/ 带尾斜杠签名
+ */
+export function getSignPageUrl() {
+ let url = window.location.href.split('#')[0]
+ if (url.endsWith('/h5walksz')) {
+ url += '/'
+ }
+ return url
+}
+
+function buildJsApiList(serverList) {
+ const required = [
+ 'getLocation',
+ 'openLocation',
+ 'updateAppMessageShareData',
+ 'updateTimelineShareData',
+ 'onMenuShareTimeline',
+ 'onMenuShareAppMessage'
+ ]
+ const merged = [...(Array.isArray(serverList) ? serverList : []), ...required]
+ return Array.from(new Set(merged))
+}
+
+/**
+ * 解析 wechat-share 返回(根级含 appId/nonceStr/timestamp/signature/jsApiList)
+ */
+export function normalizeSignaturePayload(body) {
+ if (!body) return null
+ let payload = body
+ if (typeof payload === 'string') {
+ try {
+ payload = JSON.parse(payload)
+ } catch (e) {
+ return null
+ }
+ }
+ if (payload.data && payload.data.signature) {
+ payload = payload.data
+ }
+ if (!payload.signature) return null
+ const timestamp = payload.timestamp || payload.timeStamp
+ const nonceStr = payload.nonceStr || payload.noncestr
+ if (!timestamp && timestamp !== 0) return null
+ if (!nonceStr) return null
+ return {
+ signature: payload.signature,
+ timestamp: String(timestamp),
+ nonceStr,
+ appId: payload.appId || payload.appid || WECHAT_APPID,
+ jsApiList: buildJsApiList(payload.jsApiList),
+ signUrl: payload.url || ''
+ }
+}
+
+function getAuthHeader() {
+ const token = getStoredToken()
+ return token ? { Authorization: `Bearer ${token}` } : {}
+}
+
+function parseWxSignatureResponse(res) {
+ if (!res || res.statusCode !== 200) return null
+ let body = res.data
+ if (!body) return null
+ if (body.errcode !== undefined && body.errcode !== 0) {
+ console.warn('微信签名接口返回错误:', body.errcode, body.errmsg || body.message)
+ return null
+ }
+ return normalizeSignaturePayload(body)
+}
-// 动态加载微信JS-SDK
const loadWxSDK = () => {
return new Promise((resolve, reject) => {
- // #ifdef H5
- if (typeof window !== 'undefined' && !window.wx) {
- const script = document.createElement('script')
- script.src = 'https://res.wx.qq.com/open/js/jweixin-1.6.0.js'
- script.onload = () => {
- wx = window.wx
- resolve(wx)
- }
- script.onerror = () => {
- reject(new Error('加载微信JS-SDK失败'))
- }
- document.head.appendChild(script)
- } else if (window.wx) {
- wx = window.wx
- resolve(wx)
- } else {
- reject(new Error('不支持的环境'))
+ if (typeof window === 'undefined') {
+ reject(new Error('非浏览器环境'))
+ return
}
- // #endif
- // #ifndef H5
- resolve(null)
- // #endif
+ if (window.wx) {
+ wxInstance = window.wx
+ resolve(wxInstance)
+ return
+ }
+ const script = document.createElement('script')
+ script.src = 'https://res.wx.qq.com/open/js/jweixin-1.6.0.js'
+ script.onload = () => {
+ wxInstance = window.wx
+ resolve(wxInstance)
+ }
+ script.onerror = () => reject(new Error('加载微信 JS-SDK 失败'))
+ document.head.appendChild(script)
})
}
-// 获取微信JS-SDK签名
-const getWxSignature = () => {
+function requestWxSignature(apiPath) {
+ const token = getStoredToken()
+ if (!token) {
+ return Promise.reject(new Error('未登录,无法获取微信签名'))
+ }
return new Promise((resolve, reject) => {
- // #ifdef H5
- const url = window.location.href.split('#')[0]
+ const pageUrl = getSignPageUrl()
uni.request({
- url: `${ROOTPATH}/api/mobile/user/wechat-share`,
+ url: `${ROOTPATH}${apiPath}`,
method: 'GET',
+ header: getAuthHeader(),
data: {
- url: url,
+ url: pageUrl,
activity_tag: 'walksz',
activity_list_id: 13
},
success: (res) => {
- if (res.data && res.data.signature) {
- resolve(res.data)
+ const parsed = parseWxSignatureResponse(res)
+ if (parsed) {
+ resolve(parsed)
} else {
- reject(new Error('获取签名失败'))
+ reject(new Error(`签名数据无效: ${apiPath}`))
}
},
- fail: (err) => {
- reject(err)
- }
+ fail: (err) => reject(err)
})
- // #endif
- // #ifndef H5
- resolve(null)
- // #endif
})
}
-// 初始化微信JS-SDK
-const initWxSDK = (shareConfig = {}) => {
- return new Promise((resolve, reject) => {
- // #ifdef H5
- loadWxSDK().then(() => {
- getWxSignature().then((signatureData) => {
- const { signature, timestamp, nonceStr } = signatureData
-
- wx.config({
+const getWxSignature = async () => {
+ let lastErr = null
+ for (let i = 0; i < SIGNATURE_APIS.length; i++) {
+ try {
+ return await requestWxSignature(SIGNATURE_APIS[i])
+ } catch (e) {
+ lastErr = e
+ console.warn('尝试签名接口失败:', SIGNATURE_APIS[i], e.message || e)
+ }
+ }
+ throw lastErr || new Error('获取微信签名失败')
+}
+
+function setupShare(wx, shareConfig = {}) {
+ const link = shareConfig.link || window.location.href.split('#')[0]
+ const imgUrl = shareConfig.imgUrl || `${window.location.origin}/h5walksz/static/share.jpg`
+ wx.updateAppMessageShareData({
+ title: shareConfig.title || '打卡苏州市党史教育基地',
+ desc: shareConfig.desc || '打卡苏州市党史教育基地',
+ link,
+ imgUrl
+ })
+ wx.updateTimelineShareData({
+ title: shareConfig.title || '打卡苏州市党史教育基地',
+ link,
+ imgUrl
+ })
+}
+
+/**
+ * 使用已拿到的签名数据配置 JSSDK(推荐:通过 $u.api.share 获取签名)
+ */
+export function configWechatJssdk(signatureData, options = {}) {
+ const parsed = normalizeSignaturePayload(signatureData)
+ if (!parsed) {
+ return Promise.reject(new Error('微信签名数据格式不正确'))
+ }
+ if (wxInitPromise) return wxInitPromise
+
+ const pageUrl = getSignPageUrl()
+ if (parsed.signUrl && parsed.signUrl !== pageUrl) {
+ console.warn('微信签名 URL 与当前页不一致,可能导致 config 失败', {
+ signUrl: parsed.signUrl,
+ pageUrl
+ })
+ }
+
+ wxInitPromise = loadWxSDK()
+ .then(() => {
+ return new Promise((resolve, reject) => {
+ wxInstance.config({
debug: false,
- appId: WECHAT_APPID,
- timestamp: timestamp,
- nonceStr: nonceStr,
- signature: signature,
- jsApiList: ['updateAppMessageShareData', 'updateTimelineShareData']
+ appId: parsed.appId || WECHAT_APPID,
+ timestamp: parsed.timestamp,
+ nonceStr: parsed.nonceStr,
+ signature: parsed.signature,
+ jsApiList: options.jsApiList || parsed.jsApiList || DEFAULT_JS_API
})
-
- wx.ready(() => {
- // 分享到朋友
- wx.updateAppMessageShareData({
- title: shareConfig.title || '打卡苏州市党史教育基地',
- desc: shareConfig.desc || '打卡苏州市党史教育基地',
- link: shareConfig.link || window.location.href,
- imgUrl: shareConfig.imgUrl || `${window.location.origin}/h5walksz/static/share.jpg`,
- success: () => {
- console.log('分享到朋友成功')
- }
- })
-
- // 分享到朋友圈
- wx.updateTimelineShareData({
- title: shareConfig.title || '打卡苏州市党史教育基地',
- link: shareConfig.link || window.location.href,
- imgUrl: shareConfig.imgUrl || `${window.location.origin}/h5walksz/static/share.jpg`,
- success: () => {
- console.log('分享到朋友圈成功')
- }
- })
-
- resolve(wx)
+ wxInstance.ready(() => {
+ if (options.setupShare !== false) {
+ setupShare(wxInstance, options.shareConfig)
+ }
+ if (typeof options.onReady === 'function') {
+ options.onReady(wxInstance)
+ }
+ resolve(wxInstance)
})
-
- wx.error((res) => {
- console.error('微信JS-SDK配置失败', res)
+ wxInstance.error((res) => {
+ wxInitPromise = null
+ console.error('wx.config 失败:', res)
reject(res)
})
- }).catch((err) => {
- console.error('获取签名失败', err)
- reject(err)
})
- }).catch((err) => {
- console.error('加载微信JS-SDK失败', err)
- reject(err)
})
- // #endif
- // #ifndef H5
- resolve(null)
- // #endif
+ .catch((err) => {
+ wxInitPromise = null
+ throw err
+ })
+
+ return wxInitPromise
+}
+
+export function ensureWechatJssdk(options = {}) {
+ if (!isWechatBrowser()) {
+ return Promise.reject(new Error('非微信内置浏览器'))
+ }
+ if (!getStoredToken()) {
+ return Promise.reject(new Error('请先登录后再使用微信地图能力'))
+ }
+ if (wxInitPromise) return wxInitPromise
+ return getWxSignature().then((data) => configWechatJssdk(data, options))
+}
+
+export function getWechatLocation() {
+ return ensureWechatJssdk({ setupShare: false }).then((wx) => {
+ return new Promise((resolve, reject) => {
+ wx.getLocation({
+ type: 'gcj02',
+ success: (res) => {
+ resolve({
+ longitude: res.longitude,
+ latitude: res.latitude
+ })
+ },
+ cancel: () => reject(new Error('用户取消定位')),
+ fail: (err) => reject(err)
+ })
+ })
+ })
+}
+
+export function openWechatLocation(lat, lng, name, address = '') {
+ return ensureWechatJssdk({ setupShare: false }).then((wx) => {
+ return new Promise((resolve, reject) => {
+ wx.openLocation({
+ latitude: parseFloat(lat),
+ longitude: parseFloat(lng),
+ name: name || '目的地',
+ address: address || '',
+ scale: 16,
+ success: resolve,
+ fail: reject
+ })
+ })
})
}
+const initWxSDK = (shareConfig = {}) => {
+ return ensureWechatJssdk({ shareConfig, setupShare: true })
+}
+
export {
loadWxSDK,
getWxSignature,
initWxSDK
}
-
diff --git a/manifest.json b/manifest.json
index 6288d10..10669ac 100644
--- a/manifest.json
+++ b/manifest.json
@@ -89,11 +89,7 @@
"template" : "",
"publicPath" : "/h5walksz/",
"sdkConfigs" : {
- "maps" : {
- "qqmap" : {
- "key" : "B4TBZ-G6OLU-NR6VC-GOUUX-6GTHH-BAFUZ"
- }
- }
+ "maps" : {}
}
}
}
diff --git a/pages/home/home.vue b/pages/home/home.vue
index 1086479..280a67e 100644
--- a/pages/home/home.vue
+++ b/pages/home/home.vue
@@ -25,9 +25,35 @@
+
+
+
+
+
+
+
+
+ 距你 {{ formatPointerDistance(pointer.distance) }}
+ {{ pointerAddress }}
+
+
+
+
+
+ 开始前往
+
+
+
+
+
+
+
c.key === 'address')
+ return item && item.value ? item.value : ''
+ }
+ },
+ // #endif
+ created() {
+ // #ifdef H5
+ this.defaultMarkerUrl = getDefaultMarkerUrl()
+ // #endif
+ },
onShareAppMessage() {
return shareInfo
},
@@ -220,8 +242,9 @@
},
onReady() {
this.initLocationAuth()
- // 创建地图上下文(H5端和小程序端都支持)
+ // #ifndef H5
this.mapContext = uni.createMapContext('myMap', this);
+ // #endif
// #ifndef H5
// 获取当前地图层级(小程序端)
let _this = this
@@ -249,8 +272,16 @@
},
// #ifdef H5
mounted() {
- // H5端微信分享已移除,不再初始化
- // this.initWechatShare()
+ this.defaultMarkerUrl = getDefaultMarkerUrl()
+ this.$nextTick(() => {
+ this.initTdtMap()
+ })
+ },
+ beforeDestroy() {
+ if (this.tdtMap) {
+ this.tdtMap.destroy()
+ this.tdtMap = null
+ }
},
// #endif
onLoad(option) {
@@ -300,6 +331,7 @@
console.log('H5端已有token,正常加载数据')
// H5端不显示定位提示
this.showLocationTip = false
+ this.initWechatSdkAfterLogin()
this.getArea()
this.getConfig()
this.initLocationAuth()
@@ -340,10 +372,16 @@
}
// #endif
// #ifdef H5
- // H5端不显示弹窗,直接清除pointer,让用户从列表中查看
+ this.pointer = vuex_pointer
+ if (vuex_latlng && vuex_latlng.lat && vuex_latlng.lng) {
+ this.pointer.distance = getDistance(vuex_latlng.lat, vuex_latlng.lng, this.pointer.lat, this.pointer.lng)
+ }
+ this.showPointer = true
+ this.scale = 13
uni.removeStorageSync('vuex_pointer')
- this.showPointer = false
- this.pointer = null
+ this.$nextTick(() => {
+ this.centerTdtOnPointer(this.pointer)
+ })
// #endif
}
// 答题的点位解锁
@@ -363,24 +401,17 @@
uni.removeStorageSync("vuex_point_id")
}
})
- // 更新markers中的has_answer
- this.markers.map(item => {
- if (item.id == vuex_point_id) {
- item.has_answer = 1
- }
- })
+ this.syncTdtMarkers()
// #endif
}
// #ifdef H5
- // 返回页面时,如果markers为空,重新加载点位数据
- if (this.markers.length === 0 && !isNull(this.vuex_token)) {
- console.log('onShow: markers为空,重新加载点位数据')
+ if (this.pointers.length === 0 && !isNull(this.vuex_token)) {
this.getPointers()
}
- // 如果有缓存的定位信息,使用缓存
if (vuex_latlng && vuex_latlng.lat && vuex_latlng.lng) {
this.lat = vuex_latlng.lat
this.lng = vuex_latlng.lng
+ this.updateTdtUserLocation()
}
// #endif
},
@@ -396,25 +427,219 @@
},
methods: {
// #ifdef H5
+ formatPointerDistance(distance) {
+ if (!distance && distance !== 0) return '-'
+ if (typeof distance === 'string' && (distance.includes('公里') || distance.includes('米'))) {
+ return distance
+ }
+ return distance + ' 公里'
+ },
+ gcjToTdt(lng, lat) {
+ return gcj02ToWgs84(lng, lat)
+ },
+ async initTdtMap() {
+ if (this.tdtMap) return
+ try {
+ const defaultCenter = { lng: 120.585316, lat: 31.298886 }
+ const zoom = uniScaleToTdtZoom(this.scale)
+ this.tdtMap = new TiandituMapHelper({
+ containerId: 'tdtMapContainer',
+ tk: TIANDITU_TK,
+ onMarkerClick: (e) => this.showDetail(e)
+ })
+ await this.tdtMap.init(defaultCenter.lng, defaultCenter.lat, zoom)
+ this.tdtMapReady = true
+ if (this.lat && this.lng) {
+ this.updateTdtUserLocation()
+ }
+ if (this.tdtMarkersPayload.length) {
+ await this.$nextTick()
+ await this.tdtMap.setPointMarkers(this.tdtMarkersPayload)
+ }
+ } catch (err) {
+ console.error('天地图初始化失败:', err)
+ toast('地图加载失败,请刷新重试')
+ }
+ },
+ updateTdtUserLocation() {
+ if (!this.tdtMap || !this.tdtMapReady || !this.lat || !this.lng) return
+ this.tdtMap.setUserMarker(parseFloat(this.lng), parseFloat(this.lat))
+ },
+ getMarkerIconUrl(item) {
+ const url = (item.logo && item.logo.url) || (item.image && item.image.url)
+ if (url && /^https?:\/\//i.test(url)) {
+ return url
+ }
+ return getDefaultMarkerUrl()
+ },
+ applyLocationSuccess(res) {
+ this.showLocationTip = false
+ this.isLoading = false
+ this.lng = res.longitude
+ this.lat = res.latitude
+ uni.setStorageSync('vuex_latlng', {
+ lng: this.lng,
+ lat: this.lat,
+ timestamp: Date.now()
+ })
+ this.updateTdtUserLocation()
+ this.getPointers()
+ },
+ async initWechatSdkAfterLogin() {
+ if (!isWechatBrowser()) return
+ await this.waitForToken(8000)
+ const token = this.vuex_token || (uni.getStorageSync('walksz_lifeData') || {}).vuex_token
+ if (!token) {
+ console.log('未登录,跳过微信 JSSDK 初始化')
+ return
+ }
+ resetWxJssdk()
+ const pageUrl = getSignPageUrl()
+ try {
+ const res = await this.$u.api.share({ url: pageUrl })
+ console.log('wechat-share 返回:', res)
+ const sig = normalizeSignaturePayload(res)
+ if (!sig) {
+ throw new Error('share 接口未返回 signature')
+ }
+ await configWechatJssdk(sig, { setupShare: true })
+ console.log('微信 JSSDK 初始化成功, signUrl:', sig.signUrl, 'pageUrl:', pageUrl)
+ } catch (e) {
+ console.log('微信 JSSDK 初始化失败:', e)
+ }
+ },
+ getUserLocationByWechat() {
+ return this.initWechatSdkAfterLogin()
+ .then(() => getWechatLocation())
+ .catch((err) => {
+ console.warn('微信 JSSDK 定位不可用,改用浏览器定位:', err)
+ return this.getUserLocationByBrowser()
+ })
+ },
+ getUserLocationByBrowser() {
+ return new Promise((resolve, reject) => {
+ navigator.geolocation.getCurrentPosition(
+ (pos) => {
+ const gcj = wgs84ToGcj02(pos.coords.longitude, pos.coords.latitude)
+ resolve({
+ longitude: gcj.lng,
+ latitude: gcj.lat
+ })
+ },
+ (err) => reject(err),
+ {
+ enableHighAccuracy: true,
+ timeout: 10000,
+ maximumAge: 60000
+ }
+ )
+ })
+ },
+ requestUniGetLocation() {
+ uni.getLocation({
+ type: 'gcj02',
+ timeout: 10000,
+ success: (res) => {
+ console.log('uni.getLocation 成功:', res.latitude, res.longitude)
+ this._locationFetching = false
+ this.applyLocationSuccess(res)
+ },
+ fail: (err) => {
+ console.log('uni.getLocation 失败:', err)
+ this.tryBrowserLocationAfterFail(err)
+ }
+ })
+ },
+ tryBrowserLocationAfterFail(uniErr) {
+ const errMsg = (uniErr && uniErr.errMsg) || ''
+ console.log('定位降级, 原因:', errMsg)
+ const fallback = () => {
+ this.getUserLocationByBrowser()
+ .then((res) => {
+ console.log('浏览器定位成功:', res.latitude, res.longitude)
+ this.applyLocationSuccess(res)
+ })
+ .catch((browserErr) => {
+ console.log('浏览器定位失败:', browserErr)
+ this.onLocationFinallyFailed()
+ })
+ .finally(() => {
+ this._locationFetching = false
+ })
+ }
+ if (isWechatBrowser()) {
+ this.getUserLocationByWechat()
+ .then((res) => {
+ console.log('微信 JSSDK 定位成功:', res.latitude, res.longitude)
+ this.applyLocationSuccess(res)
+ this._locationFetching = false
+ })
+ .catch(() => fallback())
+ return
+ }
+ fallback()
+ },
+ onLocationFinallyFailed() {
+ this.showLocationTip = false
+ this.isLoading = false
+ if (!isNull(this.vuex_token)) {
+ this.getPointers()
+ } else {
+ this.waitForToken(2000).then(() => {
+ if (!isNull(this.vuex_token)) this.getPointers()
+ })
+ }
+ },
+ async syncTdtMarkers() {
+ const list = await this.buildTdtMarkerList()
+ this.tdtMarkersPayload = list
+ if (!this.tdtMapReady || !this.tdtMap) return
+ await this.$nextTick()
+ await this.tdtMap.setPointMarkers(list)
+ },
+ async buildTdtMarkerList() {
+ const list = []
+ const max = Math.min(this.pointers.length, 50)
+ for (let i = 0; i < max; i++) {
+ const item = this.pointers[i]
+ const lat = parseFloat(item.lat)
+ const lng = parseFloat(item.lng)
+ if (isNaN(lat) || isNaN(lng)) continue
+ list.push({
+ id: item.id,
+ lng,
+ lat,
+ iconUrl: this.getMarkerIconUrl(item)
+ })
+ }
+ return list
+ },
+ centerTdtOnPointer(pointer) {
+ if (!pointer || !this.tdtMap || !this.tdtMapReady) return
+ const zoom = uniScaleToTdtZoom(this.scale)
+ this.tdtMap.centerAndZoom(parseFloat(pointer.lng), parseFloat(pointer.lat), zoom)
+ },
+ centerTdtOnUser() {
+ if (!this.lat || !this.lng || !this.tdtMap || !this.tdtMapReady) return
+ const zoom = uniScaleToTdtZoom(this.scale)
+ this.tdtMap.centerAndZoom(parseFloat(this.lng), parseFloat(this.lat), zoom)
+ this.updateTdtUserLocation()
+ },
// 创建圆形marker图标(带缓存)
createRoundedMarkerIcon(imageUrl) {
return new Promise((resolve) => {
- // 检查缓存
- if (this.iconCache[imageUrl]) {
- resolve(this.iconCache[imageUrl])
+ const src = resolveH5AssetUrl(imageUrl)
+ if (this.iconCache[src]) {
+ resolve(this.iconCache[src])
return
}
-
- // 如果图片已经是base64或blob,直接返回并缓存
- if (imageUrl.startsWith('data:') || imageUrl.startsWith('blob:')) {
- this.iconCache[imageUrl] = imageUrl
- resolve(imageUrl)
+ if (src.startsWith('data:') || src.startsWith('blob:')) {
+ this.iconCache[src] = src
+ resolve(src)
return
}
-
- // 创建图片对象
const img = new Image()
- img.crossOrigin = 'anonymous' // 允许跨域
+ img.crossOrigin = 'anonymous'
img.onload = () => {
try {
@@ -438,35 +663,29 @@
// 转换为base64
const base64 = canvas.toDataURL('image/png')
- // 缓存结果
- this.iconCache[imageUrl] = base64
+ this.iconCache[src] = base64
resolve(base64)
} catch (error) {
console.error('创建圆形marker图标失败:', error)
- // 缓存原图片,避免重复处理
- this.iconCache[imageUrl] = imageUrl
- resolve(imageUrl) // 失败时返回原图片
+ this.iconCache[src] = src
+ resolve(src)
}
}
-
img.onerror = () => {
- console.error('加载marker图片失败:', imageUrl)
- // 缓存原图片,避免重复处理
- this.iconCache[imageUrl] = imageUrl
- resolve(imageUrl) // 失败时返回原图片
+ console.error('加载marker图片失败:', src)
+ const fallback = this.defaultMarkerUrl || src
+ this.iconCache[src] = fallback
+ resolve(fallback)
}
-
- // 设置超时,避免长时间等待
setTimeout(() => {
if (!img.complete) {
- console.warn('marker图片加载超时:', imageUrl)
- // 缓存原图片
- this.iconCache[imageUrl] = imageUrl
- resolve(imageUrl) // 超时时返回原图片
+ console.warn('marker图片加载超时:', src)
+ const fallback = this.defaultMarkerUrl || src
+ this.iconCache[src] = fallback
+ resolve(fallback)
}
- }, 2000) // 减少超时时间到2秒
-
- img.src = imageUrl
+ }, 2000)
+ img.src = src
})
},
// #endif
@@ -501,14 +720,13 @@
})
// #endif
// #ifdef H5
- // H5端直接尝试获取定位,默认不显示提示
+ if (this._h5LocationAuthStarted) return
+ this._h5LocationAuthStarted = true
this.showLocationTip = false
- // 先显示地图容器,然后尝试获取定位
this.isLoading = true
- // 延迟一下再获取定位,确保页面先渲染
- setTimeout(() => {
+ this.$nextTick(() => {
this.getUserLocation()
- }, 100)
+ })
// #endif
},
// 我的位置
@@ -544,7 +762,7 @@
this.lng = cachedLocation.lng
this.showLocationTip = false
this.isLoading = false
- // 使用缓存定位后,如果有token则加载点位数据
+ this.updateTdtUserLocation()
if (!isNull(this.vuex_token)) {
this.getPointers()
} else {
@@ -553,87 +771,66 @@
return
}
}
- // 防抖:如果距离上次定位时间太短,不重复调用
- if (now - this.lastLocationTime < 2000) {
- console.log('定位请求过于频繁,跳过本次请求')
+ if (this._locationFetching) {
+ console.log('定位请求进行中,跳过重复调用')
return
}
+ if (this.lat && this.lng && now - this.lastLocationTime < 5000) {
+ console.log('已有定位信息,跳过重复定位')
+ this.updateTdtUserLocation()
+ if (this.pointers.length === 0) {
+ this.getPointers()
+ }
+ return
+ }
+ this._locationFetching = true
this.lastLocationTime = now
+ // 微信公众号 H5:优先微信 JSSDK 定位(gcj02)
+ if (isWechatBrowser()) {
+ this.getUserLocationByWechat()
+ .then((res) => {
+ console.log('定位成功:', res.latitude, res.longitude)
+ this.applyLocationSuccess(res)
+ this._locationFetching = false
+ })
+ .catch((err) => {
+ console.log('微信/浏览器定位失败,尝试 uni.getLocation:', err)
+ this.requestUniGetLocation()
+ })
+ return
+ }
+ this.getUserLocationByBrowser()
+ .then((res) => {
+ this.applyLocationSuccess(res)
+ this._locationFetching = false
+ })
+ .catch(() => {
+ this.requestUniGetLocation()
+ })
+ return
// #endif
uni.getLocation({
type: 'gcj02',
- // #ifdef H5
- // H5端增加超时时间
- timeout: 10000,
- // #endif
success: (res) => {
console.log('获取定位成功:', res.latitude, res.longitude)
this.showLocationTip = false
- // #ifdef H5
- this.isLoading = false
- // #endif
this.lng = res.longitude
this.lat = res.latitude
- // 保存定位信息,包含时间戳
uni.setStorageSync('vuex_latlng', {
lng: this.lng,
lat: this.lat,
timestamp: Date.now()
})
- // 获取定位后,如果有token则加载点位数据
if (!isNull(this.vuex_token)) {
- this.getPointers()
- } else {
- // #ifdef H5
- // H5端即使没有token,也可以先加载点位数据(不计算距离)
this.getPointers()
- // #endif
}
},
fail: (err) => {
- console.log("获取定位失败:", err)
- // #ifdef H5
- // H5端定位失败不显示提示,直接加载数据
- this.showLocationTip = false
- this.isLoading = false
- const errMsg = err.errMsg || err.message || '未知错误'
- console.log('定位失败原因:', errMsg)
-
- // H5端定位失败常见原因和处理建议:
- if (errMsg.includes('network error')) {
- console.warn('定位失败:网络错误,可能原因:')
- console.warn('1. 当前页面不是HTTPS协议(定位功能需要HTTPS)')
- console.warn('2. 网络连接不稳定')
- console.warn('3. 浏览器定位服务被禁用')
- // 检查是否是HTTPS
- if (window.location.protocol !== 'https:') {
- console.error('⚠️ 当前页面不是HTTPS,定位功能无法使用!请使用HTTPS协议访问。')
- }
- } else if (errMsg.includes('permission') || errMsg.includes('denied')) {
- console.warn('定位失败:用户拒绝了定位权限')
- } else if (errMsg.includes('timeout')) {
- console.warn('定位失败:定位超时')
- }
-
- // 即使定位失败,也尝试加载点位数据(不计算距离)
- if (!isNull(this.vuex_token)) {
- this.getPointers()
- } else {
- // 等待token后再加载
- this.waitForToken(2000).then(() => {
- if (!isNull(this.vuex_token)) {
- this.getPointers()
- }
- })
- }
- // #endif
- // #ifndef H5
+ console.log('获取定位失败:', err)
this.showLocationTip = true
- // 即使定位失败,如果有token也可以加载点位数据(不计算距离)
if (!isNull(this.vuex_token)) {
this.getPointers()
}
- // #endif
}
})
},
@@ -763,6 +960,7 @@
window.history.replaceState({}, '', newUrl)
// token设置完成后,再加载需要token的数据
console.log('开始加载需要token的数据')
+ this.initWechatSdkAfterLogin()
this.getArea()
this.getConfig()
this.initLocationAuth()
@@ -1015,19 +1213,8 @@
}
// #endif
// #ifdef H5
- // H5端移动地图到当前位置
- if (this.mapContext && this.lat && this.lng) {
- this.mapContext.moveToLocation({
- longitude: this.lng,
- latitude: this.lat
- });
- } else if (this.lat && this.lng) {
- // 如果mapContext不存在,直接设置scale和center
- this.scale = 13
- console.log('H5端移动到当前位置:', this.lat, this.lng)
- } else {
- console.warn('H5端没有定位信息,无法移动到当前位置')
- }
+ this.scale = 13
+ this.centerTdtOnUser()
// #endif
},
async getPointers() {
@@ -1051,188 +1238,59 @@
this.isLoading = true
// #endif
this.pointers = []
- this.markers = [] // H5端也需要生成markers
+ this.markers = []
try {
const res = await this.$u.api.getPoints({
theme_id: this.theme_id,
})
- console.log('获取点位数据成功,完整响应:', res)
- console.log('点位数量:', res.points ? res.points.length : 0)
this.pointers = res.points || []
- console.log('pointers数组:', this.pointers)
- if (this.pointers.length === 0) {
- console.warn('点位数据为空,请检查接口返回')
- }
- // #ifdef H5
- // H5端先同步生成markers,然后异步更新圆形图标
- const markerPromises = []
- // #endif
- this.pointers.map((item, index) => {
+ this.pointers.forEach((item) => {
if (this.lat && this.lng) {
- item.distance = getDistance(this.lat, this.lng, item.lat, item.lng)
+ item.distance = getDistance(this.lat, this.lng, item.lat, item.lng)
}
- // H5端和小程序端都需要生成markers
- if (index < 50) { // H5端可以显示更多点位
- // #ifdef H5
- // H5端marker格式(使用标准格式)
- const truePath = item.logo ? item.logo.url : (item.image ? item.image.url : '/static/share.jpg')
- // H5端iconPath可以使用网络图片URL
- // 优先使用truePath(网络图片),如果没有则使用本地图片
- let iconPath = truePath && truePath.startsWith('http') ? truePath : '/static/home-marker.png'
- // 如果是本地路径,转换为完整URL
- if (iconPath.startsWith('/') && !iconPath.startsWith('http')) {
- iconPath = window.location.origin + '/h5walksz' + iconPath
- }
- // 先检查是否有圆形图标缓存,如果有就直接使用
- const cachedRoundedIcon = this.iconCache && this.iconCache[iconPath]
- // 先使用原图片创建marker,后续异步更新为圆形图片
- const marker = {
+ })
+ // #ifndef H5
+ this.pointers.forEach((item, index) => {
+ if (index < 10) {
+ this.markers.push({
id: item.id,
latitude: parseFloat(item.lat),
longitude: parseFloat(item.lng),
- iconPath: cachedRoundedIcon || iconPath, // 如果有缓存就用圆形图标,否则用原图片
- originalIconPath: iconPath, // 保存原始路径,用于后续处理
- width: 40, // 增加marker尺寸,使图片更清晰
- height: 40,
+ iconPath: '/static/home-marker.png',
+ truePath: item.logo ? item.logo.url : (item.image ? item.image.url : '/static/share.jpg'),
+ width: 0,
+ height: 0,
title: item.name,
- truePath: truePath, // 保存图片路径,用于显示
- has_answer: item.has_answer, // 保存答题状态
- callout: {
- content: item.name,
- color: '#333',
- fontSize: 12,
- borderRadius: 8,
- bgColor: '#fff',
- padding: 8,
- display: 'BYCLICK',
- textAlign: 'center',
- borderWidth: 1,
- borderColor: '#e5e5e5'
- }
- }
- this.markers.push(marker)
- // 如果已经有缓存,就不需要再处理了
- if (!cachedRoundedIcon) {
- // 异步处理圆形图标
- markerPromises.push(
- this.createRoundedMarkerIcon(iconPath).then(roundedIconPath => {
- if (!roundedIconPath || roundedIconPath === iconPath) return
- const markerIndex = this.markers.findIndex(m => m.id === marker.id)
- if (markerIndex !== -1) {
- // 检查marker是否还存在(可能被重新生成了)
- const currentMarker = this.markers[markerIndex]
- if (currentMarker && currentMarker.id === marker.id) {
- // 使用Vue.set确保响应式更新,并保留所有属性
- const updatedMarker = {
- ...currentMarker,
- iconPath: roundedIconPath
- }
- this.$set(this.markers, markerIndex, updatedMarker)
- console.log('H5端更新marker圆形图标:', marker.id, 'iconPath长度:', roundedIconPath.length)
- } else {
- console.warn('H5端marker已被更新,跳过圆形图标更新:', marker.id)
- }
- }
- }).catch(err => {
- console.warn('处理marker图标失败:', err)
- })
- )
- }
- console.log('H5端生成marker:', marker.id, 'iconPath:', marker.iconPath, 'truePath:', marker.truePath)
- // #endif
- // #ifndef H5
- // 小程序端marker格式
- const marker = {
- id: item.id,
- latitude: parseFloat(item.lat),
- longitude: parseFloat(item.lng),
- iconPath: '/static/home-marker.png',
- truePath: item.logo ? item.logo.url : (item.image?item.image.url:'/static/share.jpg'),
- width: 0,
- height: 0,
- title: item.name,
- distance: item.distance ? parseInt(item.distance) : 0,
- has_answer: item.has_answer,
- customCallout: {
- anchorX: 70,
- anchorY: 30,
- display: 'ALWAYS'
- }
- }
- // #endif
- this.markers.push(marker)
+ distance: item.distance ? parseInt(item.distance) : 0,
+ has_answer: item.has_answer,
+ customCallout: {
+ anchorX: 70,
+ anchorY: 30,
+ display: 'ALWAYS'
+ },
+ })
}
})
- // #ifdef H5
- // H5端等待所有圆形图标处理完成(不阻塞主流程)
- if (markerPromises.length > 0) {
- Promise.all(markerPromises).then(() => {
- console.log('H5端所有marker圆形图标处理完成')
- // 不强制更新,让每个marker的更新自己触发
- }).catch(err => {
- console.warn('H5端处理marker图标时出错:', err)
- })
- }
// #endif
- console.log('生成的markers数量:', this.markers.length)
- console.log('markers数据:', JSON.stringify(this.markers.slice(0, 3))) // 打印前3个marker用于调试
// #ifdef H5
- console.log('H5端点位列表渲染,pointers长度:', this.pointers.length, 'markers长度:', this.markers.length)
- // 确保地图有中心点和缩放级别
if (!this.lat || !this.lng) {
- // 如果没有定位,使用第一个点位作为地图中心
- if (this.markers.length > 0) {
- this.lat = this.markers[0].latitude
- this.lng = this.markers[0].longitude
- console.log('H5端使用第一个点位作为地图中心:', this.lat, this.lng)
+ if (this.pointers.length > 0) {
+ this.lat = parseFloat(this.pointers[0].lat)
+ this.lng = parseFloat(this.pointers[0].lng)
}
}
- // 确保地图有缩放级别
if (!this.scale || this.scale < 10) {
this.scale = 13
- console.log('H5端设置地图缩放级别:', this.scale)
}
- // 强制更新markers(H5端可能需要等待地图加载完成)
- this.$nextTick(() => {
- console.log('H5端地图更新markers,当前markers数量:', this.markers.length)
- // 延迟一下,确保地图已经渲染
- setTimeout(() => {
- // 强制触发Vue的响应式更新(保留圆形图标)
- // 深拷贝markers,确保保留所有属性包括圆形图标和callout
- const markers = this.markers.map(m => {
- // 检查是否有圆形图标缓存,如果有就使用圆形图标
- const originalPath = m.originalIconPath || m.iconPath
- const cachedRoundedIcon = this.iconCache && this.iconCache[originalPath]
- const newMarker = {
- ...m,
- // 优先使用圆形图标缓存,其次使用当前的iconPath
- iconPath: cachedRoundedIcon || m.iconPath,
- }
- if (m.callout) {
- newMarker.callout = {...m.callout}
- }
- return newMarker
- })
- this.markers = []
- this.$nextTick(() => {
- this.markers = markers
- console.log('H5端强制更新markers完成,数量:', this.markers.length, '第一个iconPath类型:', typeof this.markers[0]?.iconPath)
- // 移动地图到第一个marker位置,触发地图更新
- if (this.mapContext && this.markers.length > 0) {
- this.mapContext.moveToLocation({
- longitude: this.markers[0].longitude,
- latitude: this.markers[0].latitude,
- success: () => {
- console.log('H5端地图移动到第一个marker位置成功')
- },
- fail: (err) => {
- console.error('H5端地图移动失败:', err)
- }
- })
+ if (!this.tdtMapReady) {
+ await this.initTdtMap()
+ }
+ await this.syncTdtMarkers()
+ if (this.pointers.length > 0) {
+ this.centerTdtOnPointer(this.pointers[0])
+ } else if (this.lat && this.lng) {
+ this.centerTdtOnUser()
}
- })
- }, 500)
- })
this.isLoading = false
// #endif
} catch (err) {
@@ -1295,58 +1353,79 @@
},
closeDetail() {
this.showPointer = false
- setTimeout(function() {
+ setTimeout(() => {
this.pointer = null
- }, 100);
-
+ }, 100)
},
showDetail(e) {
- // 获取markerId,兼容小程序和H5
const markerId = e.markerId || e.detail?.markerId || e.detail?.marker?.id
- console.log('showDetail触发,markerId:', markerId, 'event:', e)
const arr = this.pointers.filter(item => item.id == markerId)
if (arr.length > 0) {
this.pointer = arr[0]
if (this.lat && this.lng) {
- this.pointer.distance = getDistance(this.lat, this.lng, this.pointer.lat, this.pointer.lng)
- }
- this.showPointer = true
+ this.pointer.distance = getDistance(this.lat, this.lng, this.pointer.lat, this.pointer.lng)
+ }
+ this.showPointer = true
+ this.showNear = true
+ // #ifdef H5
+ this.centerTdtOnPointer(this.pointer)
+ // #endif
}
},
- toMap(e) {
+ async toMap(e) {
if (!this.pointer) return
// #ifndef H5
toMapAPP(this.pointer.lat, this.pointer.lng, this.pointer.name)
// #endif
// #ifdef H5
- // H5端打开腾讯地图URL
- const address = this.pointer.config && this.pointer.config.length > 0 ?
- this.pointer.config.find(c => c.key === 'address')?.value || '' : ''
- const url = this.getMapUrl(this.pointer.lat, this.pointer.lng, this.pointer.name, address)
+ if (isWechatBrowser()) {
+ try {
+ await this.initWechatSdkAfterLogin()
+ await openWechatLocation(
+ this.pointer.lat,
+ this.pointer.lng,
+ this.pointer.name,
+ this.pointerAddress
+ )
+ } catch (err) {
+ console.log('微信打开导航失败:', err)
+ toast('打开导航失败,请稍后重试')
+ }
+ return
+ }
+ const url = this.getMapUrl(this.pointer.lat, this.pointer.lng, this.pointer.name)
window.open(url, '_blank')
// #endif
},
// #ifdef H5
- // H5端:选择点位
selectPointer(item) {
this.pointer = item
- this.pointer.distance = getDistance(this.lat, this.lng, this.pointer.lat, this.pointer.lng)
+ if (this.lat && this.lng) {
+ this.pointer.distance = getDistance(this.lat, this.lng, this.pointer.lat, this.pointer.lng)
+ }
this.showPointer = true
+ this.centerTdtOnPointer(item)
},
- // H5端:打开腾讯地图URL
- openMapUrl(item) {
- const address = item.config && item.config.length > 0 ?
- item.config.find(c => c.key === 'address')?.value || '' : ''
- const url = this.getMapUrl(item.lat, item.lng, item.name, address)
+ async openMapUrl(item) {
+ if (isWechatBrowser()) {
+ const addrItem = item.config && item.config.find(c => c.key === 'address')
+ try {
+ await openWechatLocation(item.lat, item.lng, item.name, addrItem ? addrItem.value : '')
+ } catch (err) {
+ toast('打开导航失败')
+ }
+ return
+ }
+ const url = this.getMapUrl(item.lat, item.lng, item.name)
window.open(url, '_blank')
},
- // H5端:生成地图URL
- getMapUrl(lat, lng, name, address) {
- if (lat && lng) {
- const marker = `coord:${lat},${lng};title:${encodeURIComponent(name || '位置')};addr:${encodeURIComponent(address || '')}`
- return `https://apis.map.qq.com/uri/v1/marker?marker=${marker}&referer=myapp`
+ getMapUrl(gcjLat, gcjLng, name) {
+ if (gcjLat && gcjLng) {
+ const wgs = this.gcjToTdt(gcjLng, gcjLat)
+ return buildTiandituNavUrl(wgs.lng, wgs.lat, name)
}
- return 'https://apis.map.qq.com/uri/v1/marker?marker=coord:31.297241,120.580792;title:行走红色苏州;addr:苏州市姑苏区&referer=myapp'
+ const def = this.gcjToTdt(120.585316, 31.298886)
+ return buildTiandituNavUrl(def.lng, def.lat, '行走红色苏州')
},
// H5端:初始化微信分享(已移除,不再调用接口)
// initWechatShare() {
@@ -1444,19 +1523,66 @@
#myMap {
width: 100%;
height: 100vh;
- // #ifdef H5
- // H5端确保地图容器有明确的尺寸
+ }
+
+ // #ifdef H5
+ .maps-tdt {
+ width: 100%;
+ height: 100vh;
min-height: 500px;
position: relative;
-
- // 尝试通过CSS为marker图标添加圆角(如果map组件支持)
- ::v-deep .marker-icon,
- ::v-deep .marker-icon {
- border-radius: 100% !important;
- overflow: hidden;
+ z-index: 1;
+ }
+
+ .maps-info-near--h5 {
+ position: fixed;
+ right: 12px;
+ bottom: calc(78px + env(safe-area-inset-bottom, 0px));
+ z-index: 500;
+ pointer-events: auto;
+
+ image {
+ width: 123px;
+ height: 66px;
+ display: block;
}
- // #endif
}
+
+ .maps-info-pointer--h5 {
+ position: fixed;
+ left: 0;
+ right: 0;
+ bottom: calc(68px + env(safe-area-inset-bottom, 0px));
+ z-index: 501;
+ pointer-events: auto;
+ max-height: 42vh;
+ overflow-y: auto;
+ background: #fff;
+ border-radius: 16px 16px 0 0;
+ box-shadow: 0 -4px 16px rgba(0, 0, 0, 0.12);
+ }
+
+ /* 仅约束标注图标尺寸,勿用 width:auto 否则会显示原图大小 */
+ .maps-tdt ::v-deep .tdt-marker-pane {
+ z-index: 600 !important;
+ }
+
+ .maps-tdt ::v-deep img.tdt-marker-icon {
+ width: 44px !important;
+ height: 44px !important;
+ max-width: 44px !important;
+ max-height: 44px !important;
+ min-width: 0 !important;
+ min-height: 0 !important;
+ object-fit: cover !important;
+ border-radius: 50%;
+ border: 2px solid #fff;
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.25);
+ box-sizing: border-box;
+ opacity: 1 !important;
+ visibility: visible !important;
+ }
+ // #endif
// #ifdef H5
&-h5 {
@@ -1580,27 +1706,15 @@
&-near {
position: fixed;
right: 30rpx;
- // #ifndef H5
- bottom: 200rpx; // 在tabbar上方
- // #endif
- // #ifdef H5
- bottom: 100px; // H5端使用px单位,在tabbar上方
- // #endif
- z-index: 9999; // 提高层级,确保在地图之上
- cursor: pointer; // H5端添加鼠标指针样式
- transition: bottom 0.3s ease-in-out; // 添加过渡动画
-
+ bottom: 200rpx;
+ z-index: 99;
+ cursor: pointer;
+
image {
width: 246rpx;
height: 132rpx;
- pointer-events: auto; // 确保可以点击
}
-
-
}
- &.maps-info-near-up {
- bottom: 420rpx !important;
- }
}
// #endif
@@ -1610,13 +1724,6 @@
bottom: 160rpx;
width: 100%;
z-index: 99;
-
- // #ifdef H5
- position: fixed;
- bottom: 180rpx; // H5端为tabbar留出空间
- z-index: 9999; // H5端提高层级,确保在地图之上
- pointer-events: none; // 容器不拦截事件,让子元素可以点击
- // #endif
&-pointer {
background: #fff;
@@ -1627,9 +1734,8 @@
font-size: 28rpx;
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.1);
// #ifdef H5
- pointer-events: auto; // H5端确保可以点击
- border-radius: 20px 20px 0 0;
- box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);
+ border-radius: 16px 16px 0 0;
+ box-shadow: 0 -4px 16px rgba(0, 0, 0, 0.12);
// #endif
&-header {