小程序
微信服务号注册错误次数过多,导致现在注册不了,怎么办,只能等一个月吗?
小程序虚拟支付 IOS 跳转支付,订单状态是支付成功,虚拟支付资金管理 页面也没显示 但是不扣款什么问题? [图片]
https://developers.weixin.qq.com/miniprogram/dev/server/getting_started/api_signature.htmlhttps://developers.weixin.qq.com/miniprogram/dev/server/API/VirtualPayment/api_start_upload_goods.html
小程序虚拟支付 IOS 跳转支付,订单状态是支付成功,虚拟支付资金管理 页面也没显示 但是不扣款什么问题?
[图片][图片][图片]
如图,希望尽快适配。 [图片]
[图片]我们小程序上导出的交易订单数据,每个月和你的这个数据都不一样,又看不到你这个数据明细在哪里,只能看到每天多少,每天的数据明细有没有按笔能导出,类似银行明细,每一笔都能导出。请问在哪里能导出每个月的每笔明细。
小程序虚拟支付 IOS 跳转支付,订单状态是支付成功 但是不扣款什么问题?
小程序的头像发生了变化,但是小程序的微信客服头像还是之前的头像,如何设置 [图片][图片]
你好,我的小程序已正式发布,商户号、AppID 绑定正常,JSAPI 支付已开通,服务端统一下单也能成功返回 prepay_id,但客户端调用 wx.requestPayment 时提示: 1)由于小程序违规,支付功能暂时无法使用 2)requestPayment:fail jsapi has no permission
初审驳回。去完善后重新提交。系统显示主体仍在备案当中。无法进行下一步操作。也无法撤销。
[图片] 新开发的小程序,重新发布后还是这样,一直不能用 [图片]
[图片]
我的小程序备案,因错过短信核验,后台状态卡在‘管局审核中’已经超过24小时,没有出现‘重新提交’按钮,请协助处理。
正常使用的小程序,文字不能显示,这个人反应,也是没有渠道
您好,域名(c1u.cn),工作室官网,域名在腾讯云注册并备案通过,服务器也在腾讯云,浏览器访问正常,我是网站开发者,访问时间如图2026/04/05,微信访问网站出现“无法确认该网页的安全性,请谨慎访问”,经检查网页里没有不安全的内容,请核实后帮忙解除拦截,谢谢! [图片] [图片]
有什么正规途径可以获取当前Tencent境外小程序的现状?数字娱乐、本地服务、通用工具、AI工具的数量这种数据
使用 addGroundOverlay添加手绘地图覆盖物,API调用成功,但 groundOverlays数组始终为空 图片URL可正常访问,配置正确,bounds坐标准确 开发者工具和真机均不显示覆盖物 微信版本:8.0.48 基础库版本:3.14.3 操作系统:Windows 11 / iOS 16 / Android 13 开发者工具版本:最新稳定版 小程序AppID:wxff75d3fb22ba667a 怀疑方向: addGroundOverlayAPI 在特定条件下不更新数据绑定 地图组件内部实现有bug 生命周期或调用时机问题 请求官方协助: 这是否是已知的bug? 是否有正确的调用示例? 需要特殊配置吗? // pages/map/map.js // 总调度中心 - 仅负责导入和代理调用 // 引入所有服务模块 const uiService = require('../../services/ui-service'); const mapService = require('../../services/map-service'); const audioService = require('../../services/audio-service'); const locationService = require('../../services/location-service'); const tourService = require('../../services/tour-service'); // 引入工具函数 const utils = require('../../utils/index'); Page({ // 数据层:完整复制原始map.js的data对象 data: { // 地图相关 mapScale: 18, mapCenterLatitude: 30.8, mapCenterLongitude: 120.3, userLocation: null, mapSpots: [], mapImageUrl: '', groundOverlays: [], usingTencentMap: true, locationPermissionDenied: false, // 手绘地图相关新增字段 handdrawSpots: [], // 手绘地图标点数据 mapImageSize: { width: 0, height: 0 }, // 手绘地图图片尺寸 mapConfig: null, // 地图配置 handdrawConfig: null, // 手绘地图配置 hasShownFallbackToast: false, // 是否已显示降级提示 showSpotLabels: true, // 是否显示标点标签 // 模式相关 currentMode: 'none', // 'none', 'exclusive', 'guided_tour' isTourLocked: false, isTourPaused: false, globalTourLocked: false, // UI状态 showSpotActionSheet: false, selectedSpot: null, loadingText: '', isRequesting: false, lastTapTime: 0, showModeActionSheet: false, // === 【优化方案新增字段 START】=== // 伴游智能防抖与冷却相关 lastValidLocation: null, // 上一次有效位置 {latitude, longitude} lastUpdateTime: 0, // 【新增】上一次有效位置更新的时间戳(毫秒) accumulatedDistance: 0, // 累计移动距离(米) frontendCooldown: 300000, // 前端冷却时间5分钟 (300000毫秒) lastTourRequestTime: 0, // 上次触发时间戳 // === 【优化方案新增字段 END】=== // 伴游相关状态 tourState: { state: 'idle', // 'idle', 'playing', 'paused' currentSpot: null, visitedSpotIds: [], lastRequestTime: 0 }, // 注意:lastTourRequestTime 字段已上移至新增字段区,此处data中已不存在,后续代码中所有 this.data.lastTourRequestTime 将引用新的字段 tourLocationListener: null, lastTriggeredSpotId: '', // 音频播放器 audioPlayer: { show: false, title: '', data: null, playing: false, paused: false, hasImage: false, imageUrl: '', hasVoice: false, answerText: '', friendlySource: '', currentSpotId: '' }, innerAudioContext: null, // ====================== 【新增调试字段】====================== debug: { isMapComponentReady: false, // 地图组件自身是否已触发 ready mapReadyTimestamp: null, // 地图组件就绪的时间戳 pageReadyTimestamp: null, // 页面 onReady 的时间戳 lastGroundOverlaySetTime: null, // 最后一次设置覆盖物的时间 groundOverlaysQueue: [], // 覆盖物设置队列(用于地图未就绪时缓存) logs: [] // 存储调试日志 } }, // ====================== 生命周期函数代理 ====================== onLoad: function (options) { this._addDebugLog('[生命周期] 页面 onLoad 开始'); tourService.initTourState(this); locationService.getUserLocationAndInitMap(this); }, onShow: function () { const app = getApp(); const isLocked = app.isTourModeLocked(); this.setData({ globalTourLocked: isLocked, isTourLocked: isLocked && this.data.currentMode === 'guided_tour' }); if (isLocked && this.data.currentMode === 'guided_tour' && !this.data.isTourPaused) { locationService.startLocationUpdateForTour(this); } }, onUnload: function () { locationService.stopLocationUpdateForTour(this); audioService.stopCurrentVoicePlayback(this); audioService.closeAudioPlayer(this); }, onHide: function () { locationService.stopLocationUpdateForTour(this); }, onReady: function() { console.log('✅ 页面onReady'); // 初始化地图上下文 this.mapContext = wx.createMapContext('tencentMap', this); console.log('🗺️ 地图上下文初始化完成'); // 【新增】记录页面就绪时间 this.setData({ 'debug.pageReadyTimestamp': Date.now() }); this._addDebugLog(`[生命周期] 页面 onReady 执行。地图上下文已创建。`); this._addDebugLog(`[状态] 当前 groundOverlays 数据: ${JSON.stringify(this.data.groundOverlays)}`); // 【修改】删除延迟检查手绘地图的代码,因为已经在loadMapAndNearbySpots中处理 // 手绘地图加载已由 mapService.loadMapAndNearbySpots 统一处理 }, // ====================== 【核心修改】地图组件就绪事件处理函数 ====================== /** * 地图组件就绪事件处理函数 */ onMapComponentReady: function() { const now = Date.now(); const pageReadyTime = this.data.debug.pageReadyTimestamp; const timeDiff = pageReadyTime ? now - pageReadyTime : 'N/A'; this._addDebugLog(`[地图组件] bindready 事件触发!当前时间戳: ${now}`); this._addDebugLog(`[时间线] 页面onReady 到 地图bindready 间隔: ${timeDiff}ms`); // 更新地图上下文(确保是最新的) this.mapContext = wx.createMapContext('tencentMap', this); this._addDebugLog(`[地图组件] 地图上下文重新创建完成。`); // 更新就绪状态 this.setData({ 'debug.isMapComponentReady': true, 'debug.mapReadyTimestamp': now }); // 检查是否有缓存的覆盖物需要设置 const queue = this.data.debug.groundOverlaysQueue; if (queue && queue.length > 0) { this._addDebugLog(`[队列] 发现 ${queue.length} 个缓存的覆盖物,开始应用...`); this.setData({ groundOverlays: queue, 'debug.groundOverlaysQueue': [], 'debug.lastGroundOverlaySetTime': now }, () => { this._addDebugLog(`[队列] 缓存覆盖物应用完成。当前 groundOverlays: ${JSON.stringify(this.data.groundOverlays)}`); this._forceMapRedrawWithLog(); }); } else { this._addDebugLog(`[队列] 覆盖物队列为空,无需应用。`); // 即使没有队列,也检查当前已设置的覆盖物,并尝试重绘 if (this.data.groundOverlays && this.data.groundOverlays.length > 0) { this._addDebugLog(`[状态] 地图就绪时发现已存在 groundOverlays,尝试重新激活。`); this._reactivateGroundOverlays(); } } // 输出完整的调试摘要 this._printDebugSummary(); }, // ====================== 【新增调试函数】====================== /** * 添加调试日志 */ _addDebugLog: function(message) { console.log(`[GroundOverlay调试] ${message}`); const timestamp = new Date().toLocaleTimeString('zh-CN', { hour12: false, fractionalSecondDigits: 3 }); const newLog = `${timestamp} - ${message}`; // 保持日志数组不至于过长 const currentLogs = this.data.debug.logs || []; const updatedLogs = [newLog, ...currentLogs].slice(0, 50); // 保留最近50条 this.setData({ 'debug.logs': updatedLogs }); }, /** * 重新激活已存在的覆盖物(通过先清空再设置的方式) */ _reactivateGroundOverlays: function() { const currentOverlays = this.data.groundOverlays; this._addDebugLog(`[重绘] 开始重新激活 ${currentOverlays.length} 个覆盖物。`); this.setData({ groundOverlays: [] }, () => { setTimeout(() => { this.setData({ groundOverlays: currentOverlays, 'debug.lastGroundOverlaySetTime': Date.now() }, () => { this._addDebugLog(`[重绘] 覆盖物重新激活完成。`); this._forceMapRedrawWithLog(); }); }, 150); }); }, /** * 增强的强制地图重绘函数(带日志) */ _forceMapRedrawWithLog: function() { this._addDebugLog(`[重绘] 开始强制地图重绘流程...`); const originalScale = this.data.mapScale || 18; const originalLat = this.data.mapCenterLatitude; const originalLng = this.data.mapCenterLongitude; if (!originalLat || !originalLng) { this._addDebugLog(`[重绘] ❌ 中止:地图中心坐标无效。`); return; } // 微调缩放级别以触发重绘 this.setData({ mapScale: originalScale + 0.001 }, () => { setTimeout(() => { this.setData({ mapScale: originalScale }, () => { this._addDebugLog(`[重绘] ✅ 通过缩放变化触发重绘完成。`); }); }, 50); }); }, /** * 打印调试摘要 */ _printDebugSummary: function() { this._addDebugLog(`========== 调试状态摘要 ==========`); this._addDebugLog(`地图组件就绪: ${this.data.debug.isMapComponentReady}`); this._addDebugLog(`覆盖物队列长度: ${(this.data.debug.groundOverlaysQueue || []).length}`); this._addDebugLog(`页面Data中覆盖物长度: ${(this.data.groundOverlays || []).length}`); this._addDebugLog(`========== 摘要结束 ==========`); }, /** * (可选)手动触发调试信息检查 */ manualDebugCheck: function() { wx.showModal({ title: '手动调试', content: '将在控制台输出当前覆盖物和地图状态的详细信息。', success: (res) => { if (res.confirm) { this._printDebugSummary(); this._addDebugLog(`[手动检查] 当前 groundOverlays 详情: ${JSON.stringify(this.data.groundOverlays, null, 2)}`); this._addDebugLog(`[手动检查] 地图上下文存在: ${!!this.mapContext}`); this._addDebugLog(`[手动检查] 地图中心: ${this.data.mapCenterLatitude}, ${this.data.mapCenterLongitude}`); this._addDebugLog(`[手动检查] 地图缩放: ${this.data.mapScale}`); } } }); }, // ====================== UI 控制函数代理 ====================== showLoading: function (text) { uiService.showLoading(text, this); }, hideLoading: function () { uiService.hideLoading(this); }, isButtonDebouncing: function () { return uiService.isButtonDebouncing(this); }, showModeSelect: function () { uiService.showModeSelect(this); }, hideModeSelect: function () { uiService.hideModeSelect(this); }, hideSpotAction: function () { uiService.hideSpotAction(this); }, onMapZoomIn: function () { uiService.onMapZoomIn(this); }, onMapZoomOut: function () { uiService.onMapZoomOut(this); }, onResetMap: function () { uiService.onResetMap(this); }, // ====================== 地图与景点函数代理 ====================== onMapMarkerTap: function (e) { mapService.onMapMarkerTap(e, this); }, onMapSpotTap: function (e) { mapService.onMapSpotTap(e, this); }, loadMapAndNearbySpots: function (centerLat, centerLng) { mapService.loadMapAndNearbySpots(centerLat, centerLng, this); }, useDefaultLocation: function () { mapService.useDefaultLocation(this); }, parseLocationString: function(locationObj) { return mapService.parseLocationString(locationObj); }, /** * 手绘地图图片加载完成事件 * @param {Object} e - 图片加载事件对象 */ onMapImageLoad: function(e) { const { width, height } = e.detail; console.log(`手绘地图图片加载完成,尺寸: ${width}x${height}`); this.setData({ mapImageSize: { width, height } }); // 触发坐标转换 if (this.data.mapSpots.length > 0 && this.data.handdrawConfig) { mapService.convertSpotsToHanddraw(this); } }, // ====================== 音频播放函数代理 ====================== previewImage: function (e) { audioService.previewImage(e, this); }, pauseOrResumeAudio: function () { audioService.pauseOrResumeAudio(this); }, startAudioPlayback: function () { audioService.startAudioPlayback(this); }, closeAudioPlayer: function () { audioService.closeAudioPlayer(this); }, // 【修复】添加缺失的关闭音频播放器代理 onSpotExplain: function () { audioService.onSpotExplain(this); }, onSpotNavigate: function () { audioService.onSpotNavigate(this); }, // ====================== 定位与伴游函数代理 ====================== handleSelectExclusiveMode: function () { tourService.handleSelectExclusiveMode(this); }, handleSelectTourMode: function () { tourService.handleSelectTourMode(this); }, pauseTourMode: function () { tourService.pauseTourMode(this); }, resumeTourMode: function () { tourService.resumeTourMode(this); }, onExitTourMode: function () { tourService.onExitTourMode(this); }, exitTourModeCleanup: function () { tourService.exitTourModeCleanup(this); }, onLocationChangeForTour: function (res) { tourService.onLocationChangeForTour(res, this); }, // ====================== 工具函数直接导入 ====================== calculateDistance: utils.calculateDistance, // ====================== 【新增】地图相关函数 ====================== /** * 【修改】检查手绘地图 * 【重要修改】手绘地图的加载应该由 map-service.js 统一处理 * 这个函数现在只用于调试目的,不实际加载手绘地图 */ checkHanddrawMap: function() { console.log('🔍 检查手绘地图状态'); console.log('当前地图配置:', this.data.mapConfig); console.log('当前groundOverlays数量:', this.data.groundOverlays.length); console.log('groundOverlays详情:', this.data.groundOverlays); // 【修改】不再手动加载手绘地图,因为已经在 loadMapAndNearbySpots 中处理 // 手绘地图的加载统一由 mapService.loadMapAndNearbySpots 处理 }, /** * 【修改】地图更新完成事件 * 只用于状态检查和调试,不触发任何加载操作 */ onMapUpdated: function(e) { console.log('✅ 地图组件updated事件触发'); console.log('当前groundOverlays数量:', this.data.groundOverlays.length); console.log('groundOverlays详情:', this.data.groundOverlays); if (this.data.groundOverlays.length > 0) { console.log('🎉 groundOverlays应该已显示在地图上'); // 检查图片是否加载成功 const overlay = this.data.groundOverlays[0]; console.log('图片URL:', overlay.src); console.log('边界范围:', overlay.bounds); console.log('透明度:', overlay.opacity); } else { console.log('❌ groundOverlays为空'); // 【重要】这里不再调用 checkHanddrawMap 或其他加载函数 // 手绘地图的加载已由 map-service.js 统一处理 } }, /** * 【保留】直接测试groundOverlays * 这个函数用于调试,可以手动调用 */ testGroundOverlays: function() { console.log('🧪 开始测试groundOverlays显示'); // 使用一个绝对简单的测试图片和边界 const testOverlay = { id: 999, // 使用大数字,避免冲突 src: 'https://img2.baidu.com/it/u=3040262610,2207575815&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=500', bounds: { southwest: { latitude: 30.74, longitude: 120.15 }, northeast: { latitude: 30.76, longitude: 120.17 } }, opacity: 0.8, // 设置透明度为0.8,更容易看到 zIndex: 9999 }; console.log('📋 测试groundOverlay对象:', testOverlay); // 先清空 this.setData({ groundOverlays: [] }, () => { setTimeout(() => { // 再设置 this.setData({ groundOverlays: [testOverlay], mapCenterLatitude: 30.75, mapCenterLongitude: 120.16, mapScale: 18 }, () => { console.log('✅ 测试groundOverlays已设置'); console.log('当前groundOverlays:', this.data.groundOverlays); // 强制重绘 this.forceMapRedraw(); }); }, 100); }); }, /** * 【保留】强制地图重绘 */ forceMapRedraw: function() { console.log('🔄 手动触发地图重绘'); const originalScale = this.data.mapScale; const originalLat = this.data.mapCenterLatitude; const originalLng = this.data.mapCenterLongitude; if (!originalLat || !originalLng) { console.warn('❌ 地图中心坐标无效,无法重绘'); return; } // 轻微改变地图参数 this.setData({ mapScale: originalScale + 0.001 }, () => { setTimeout(() => { this.setData({ mapScale: originalScale }, () => { console.log('✅ 手动重绘完成'); // 通过地图上下文再次触发 if (this.mapContext) { this.mapContext.moveToLocation({ latitude: originalLat + 0.00001, longitude: originalLng }); setTimeout(() => { this.mapContext.moveToLocation({ latitude: originalLat, longitude: originalLng }); }, 100); } }); }, 100); }); }, // ====================== 【新增】统一权限校验与相关功能入口修改 ====================== /** * 【新增】统一的定位权限检查与申请函数 * @param {string} actionDesc - 触发申请的功能描述(用于提示用户),例如:"智能伴游"、"路线导航" * @param {Function} successCallback - 授权成功并获取位置后的回调函数 * @param {Function} failCallback - 用户拒绝授权或最终失败后的回调函数(可选) */ checkAndRequestLocation: function (actionDesc, successCallback, failCallback) { const that = this; console.log(`[权限检查] 功能: ${actionDesc}, 当前状态: locationPermissionDenied=${that.data.locationPermissionDenied}, userLocation=${that.data.userLocation ? '有' : '无'}`); // 情景1:已有精确定位,直接执行成功回调 if (this.data.userLocation && !this.data.locationPermissionDenied) { console.log('[权限检查] 已有定位权限和位置,直接执行功能'); typeof successCallback === 'function' && successCallback(); return; } // 情景2:权限曾被拒绝,需引导用户前往设置页手动开启 if (this.data.locationPermissionDenied) { wx.showModal({ title: `开启${actionDesc}`, content: `此功能需要获取您的实时位置。您之前已拒绝过定位权限,需要前往小程序设置页手动开启。`, confirmText: '去设置', cancelText: '取消', success: (modalRes) => { if (modalRes.confirm) { // 用户确认,打开小程序设置页 console.log(`[权限检查] 用户同意为【${actionDesc}】打开设置页`); wx.openSetting({ success: (settingRes) => { console.log('[权限检查] 用户从设置页返回', settingRes); // 无论用户在设置页是否操作,返回后都尝试重新获取位置 wx.showLoading({ title: '获取位置中...' }); locationService.getUserLocationAndInitMap(that); // 等待定位结果 that._waitForLocationUpdate(actionDesc, successCallback, failCallback); }, fail: (err) => { console.error('[权限检查] 打开设置页失败', err); wx.showToast({ title: '打开设置失败', icon: 'none' }); typeof failCallback === 'function' && failCallback(); } }); } else { // 用户取消,不前往设置 console.log(`[权限检查] 用户取消为【${actionDesc}】打开设置页`); typeof failCallback === 'function' && failCallback(); } } }); return; } // 情景3:首次使用或无位置,但未明确拒绝 (例如网络失败、首次触发) console.log(`[权限检查] 首次为【${actionDesc}】申请定位权限`); wx.showLoading({ title: '获取位置中...' }); locationService.getUserLocationAndInitMap(this); // 等待定位结果 that._waitForLocationUpdate(actionDesc, successCallback, failCallback); }, /** * 【新增】私有方法:等待位置更新完成 * @param {string} actionDesc - 功能描述 * @param {Function} successCallback - 成功回调 * @param {Function} failCallback - 失败回调 */ _waitForLocationUpdate: function (actionDesc, successCallback, failCallback) { const that = this; let checkCount = 0; const maxCheckCount = 20; // 最多检查10秒 (20 * 500ms) const checkInterval = setInterval(() => { checkCount++; // 检查条件:获取到了用户位置 且 权限未被拒绝 if (that.data.userLocation && !that.data.locationPermissionDenied) { clearInterval(checkInterval); wx.hideLoading(); console.log(`[权限检查] 【${actionDesc}】定位授权成功,位置已获取`); typeof successCallback === 'function' && successCallback(); } // 检查条件:权限被拒绝(可能在这次申请中再次被拒) else if (that.data.locationPermissionDenied) { clearInterval(checkInterval); wx.hideLoading(); console.log(`[权限检查] 【${actionDesc}】定位授权被拒绝`); wx.showToast({ title: '需要位置权限', icon: 'none' }); typeof failCallback === 'function' && failCallback(); } // 检查条件:超时 else if (checkCount > maxCheckCount) { clearInterval(checkInterval); wx.hideLoading(); console.warn(`[权限检查] 【${actionDesc}】等待位置更新超时`); wx.showToast({ title: '获取位置超时', icon: 'none' }); typeof failCallback === 'function' && failCallback(); } // 否则继续等待 }, 500); }, /** * 【修改】智能伴游模式入口 - 加入权限检查 */ handleSelectTourMode: function () { this.checkAndRequestLocation( '智能伴游模式', () => { // 授权成功后的回调:执行原有的伴游模式启动逻辑 console.log('[handleSelectTourMode] 定位就绪,开始执行伴游模式逻辑'); tourService.handleSelectTourMode(this); }, () => { // 授权失败后的回调 wx.showToast({ title: '需要位置权限才能开始伴游', icon: 'none' }); } ); }, /** * 【修改】地图复位功能入口 - 加入权限检查 */ onResetMap: function () { const app = getApp(); // 如果已有用户位置,直接使用它复位 if (this.data.userLocation && !this.data.locationPermissionDenied) { uiService.onResetMap(this); return; } // 如果没有位置或权限被拒,触发权限检查和获取 this.checkAndRequestLocation( '重置地图视野', () => { // 授权并获取位置成功后,执行复位逻辑 console.log('[onResetMap] 定位就绪,执行地图复位'); uiService.onResetMap(this); }, () => { // 用户最终拒绝授权,降级到使用默认位置复位 console.log('[onResetMap] 定位授权失败,使用默认位置复位'); mapService.useDefaultLocation(this); // 给用户一个提示 wx.showToast({ title: '已使用默认位置', icon: 'none' }); } ); }, /** * 【修改】景点导航功能入口 - 加入权限检查 */ onSpotNavigate: function () { this.checkAndRequestLocation( '路线导航', () => { // 授权成功后的回调:执行原有的导航逻辑 console.log('[onSpotNavigate] 定位就绪,开始执行导航逻辑'); audioService.onSpotNavigate(this); }, () => { // 授权失败后的回调 wx.showToast({ title: '需要位置权限才能导航', icon: 'none' }); } ); } }) <!-- pages/map/map.wxml --> <view class="page-container"> <!-- 顶部状态栏 --> <view class="status-bar"> <view class="mode-indicator" wx:if="{{currentMode !== 'none'}}"> <text class="mode-text">{{currentMode === 'exclusive' ? '专属导游' : '智能伴游'}}</text> <text class="exit-btn" wx:if="{{currentMode === 'guided_tour' && isTourLocked}}" bindtap="onExitTourMode">退出</text> </view> <view class="mode-selector-btn {{currentMode !== 'none' ? 'active' : ''}}" bindtap="showModeSelect"> <text class="btn-icon">📍</text> <text class="btn-text">{{currentMode === 'none' ? '选择模式' : '切换模式'}}</text> </view> </view> <!-- 地图区域 - 统一使用腾讯地图组件(包含地面覆盖物) --> <view class="map-area"> <!-- 腾讯地图组件(始终显示,支持地面覆盖物显示手绘地图) --> <map id="tencentMap" class="tencent-map" latitude="{{mapCenterLatitude}}" longitude="{{mapCenterLongitude}}" scale="{{mapScale}}" markers="{{mapSpots}}" ground-overlays="{{groundOverlays}}" bindmarkertap="onMapMarkerTap" bindupdated="onMapUpdated" bindready="onMapComponentReady" show-location > </map> <!-- 用户位置标记(伴游模式) --> <view wx:if="{{userLocation && currentMode === 'guided_tour'}}" class="user-location-marker" style="top: 50%; left: 50%; transform: translate(-50%, -50%);"> <view class="pulse-dot"></view> </view> <!-- 【修复点】将 catchtouchmove="preventDefault" 改为 catchtouchmove --> <view wx:if="{{globalTourLocked}}" class="tour-lock-overlay" > <view class="lock-message"> <text class="lock-icon">🔒</text> <text class="lock-text">智能伴游模式运行中</text> <text class="lock-tip">屏幕已锁定,请专注于语音讲解</text> <text class="lock-exit-tip">点击顶部"退出"按钮结束伴游</text> <!-- ================= 【新增】伴游模式暂停/恢复控制按钮 ================= --> <view class="tour-control-buttons"> <button wx:if="{{!isTourPaused}}" class="tour-control-btn btn-pause" bindtap="pauseTourMode" > ⏸️ 暂停伴游 </button> <button wx:if="{{isTourPaused}}" class="tour-control-btn btn-resume" bindtap="resumeTourMode" > ▶️ 恢复伴游 </button> </view> </view> </view> <!-- 【新增】测试按钮,完成后可删除 --> <view class="test-groundoverlay-btn" bindtap="testGroundOverlays"> <text>🧪 测试GroundOverlays</text> </view> </view> <!-- 地图控制按钮 (右下角) --> <view class="map-controls"> <view class="control-btn zoom-in" bindtap="onMapZoomIn">+</view> <view class="control-btn zoom-out" bindtap="onMapZoomOut">-</view> <view class="control-btn reset" bindtap="onResetMap">⌂</view> </view> <!-- 模式选择 ActionSheet --> <view class="action-sheet-overlay {{showModeActionSheet ? 'show' : ''}}" catchtap="hideModeSelect"> <view class="action-sheet-content" catchtap="stopPropagation"> <view class="action-sheet-title">选择导游模式</view> <view class="action-sheet-item exclusive" bindtap="handleSelectExclusiveMode"> <view class="item-icon">🎤</view> <view class="item-text"> <view class="item-title">专属导游模式</view> <view class="item-desc">点击地图景点,获取深度讲解和导航</view> </view> </view> <view class="action-sheet-item tour" bindtap="handleSelectTourMode"> <view class="item-icon">🚶</view> <view class="item-text"> <view class="item-title">智能伴游模式</view> <view class="item-desc">走到哪讲到哪,自动讲解附近景点</view> </view> </view> <view class="action-sheet-item cancel" bindtap="hideModeSelect">取消</view> </view> </view> <!-- 景点操作 ActionSheet --> <view class="action-sheet-overlay {{showSpotActionSheet ? 'show' : ''}}" catchtap="hideSpotAction"> <view class="action-sheet-content" catchtap="stopPropagation"> <view class="action-sheet-title">{{selectedSpot ? selectedSpot.name : ''}}</view> <!-- 【修改点】根据当前模式动态显示讲解按钮文本 --> <view class="action-sheet-item explain" bindtap="onSpotExplain"> <view class="item-icon">🔊</view> <view class="item-text"> {{currentMode === 'exclusive' ? '景点讲解' : '重新讲解'}} </view> </view> <view class="action-sheet-item navigate" bindtap="onSpotNavigate"> <view class="item-icon">🧭</view> <view class="item-text">路线导航</view> </view> <view class="action-sheet-item cancel" bindtap="hideSpotAction">取消</view> </view> </view> <!-- 全局加载遮罩 --> <view wx:if="{{loadingText}}" class="loading-overlay"> <view class="loading-content"> <view class="loading-spinner"></view> <text class="loading-text">{{loadingText}}</text> </view> </view> <!-- ================= 【新增修复2】自定义答案模态框 ================= --> <view wx:if="{{showAnswerModal}}" class="answer-modal"> <view class="modal-mask" bindtap="hideAnswerModal"></view> <view class="modal-content"> <view class="modal-header"> <text class="modal-title">{{answerModalTitle}}</text> <view class="modal-close" bindtap="hideAnswerModal">×</view> </view> <view class="modal-body"> <text class="answer-text">{{answerModalContent}}</text> <!-- 语音播放按钮区域 --> <view class="voice-action-section"> <button class="voice-play-button" bindtap="playAnswerVoice" > 🔊 播放语音讲解 </button> <text class="voice-tip">点击播放语音讲解</text> </view> </view> <view class="modal-footer"> <button class="modal-btn confirm" bindtap="hideAnswerModal">关闭</button> </view> </view> </view> <!-- ================= 【新增修复3】语音播放器 ================= --> <view class="voice-player" wx:if="{{showVoicePlayer}}"> <view class="voice-player-header"> <text class="voice-player-title">{{voiceTitle}}</text> <view class="voice-player-close" bindtap="closeVoicePlayer">×</view> </view> <view class="voice-player-content"> <view class="voice-text">{{voiceText}}</view> <view class="voice-controls"> <view class="voice-progress"> <view class="voice-progress-bar" style="width: {{voiceProgress}}%"></view> </view> <view class="voice-buttons"> <button class="voice-btn play-btn {{isPlaying ? 'playing' : ''}}" bindtap="toggleVoicePlayback" > {{isPlaying ? '暂停' : '播放'}} </button> <button class="voice-btn stop-btn" bindtap="stopVoicePlayback" wx:if="{{isPlaying}}" > 停止 </button> </view> <view class="voice-time"> <text>{{currentTime}} / {{totalTime}}</text> </view> </view> </view> </view> <!-- ================= 【重构】多媒体讲解器 (图片+文字+语音) ================= --> <view wx:if="{{audioPlayer.show}}" class="multimedia-player-container"> <!-- 遮罩层 --> <view class="player-mask" bindtap="closeAudioPlayer"></view> <!-- 播放器主体 --> <view class="multimedia-player"> <!-- 头部:标题和关闭按钮 --> <view class="player-header"> <!-- 【修改点】将标题和友好来源组合显示 --> <text class="player-title">{{audioPlayer.title}} · {{audioPlayer.friendlySource}}</text> <view class="player-close-btn" bindtap="closeAudioPlayer">×</view> </view> <!-- 内容区域 --> <view class="player-content"> <!-- 图片区域 (有图片时显示) --> <view wx:if="{{audioPlayer.hasImage}}" class="image-section"> <image src="{{audioPlayer.imageUrl}}" mode="aspectFit" class="spot-image" bindtap="previewImage" data-src="{{audioPlayer.imageUrl}}" /> <view class="image-tip">点击图片可放大查看</view> </view> <!-- 文字讲解区域 --> <view wx:if="{{audioPlayer.answerText}}" class="text-section"> <view class="section-title">文字讲解</view> <view class="answer-text">{{audioPlayer.answerText}}</view> </view> <!-- 语音控制区域 (有语音时显示) --> <view wx:if="{{audioPlayer.hasVoice}}" class="voice-control-section"> <view class="section-title">语音讲解</view> <!-- 播放控制按钮 --> <view class="voice-controls"> <button wx:if="{{!audioPlayer.playing && !audioPlayer.paused}}" class="btn-control btn-play" bindtap="startAudioPlayback" size="mini" > 🔊 开始播放 </button> <button wx:if="{{audioPlayer.playing || audioPlayer.paused}}" class="btn-control btn-pause-resume" bindtap="pauseOrResumeAudio" size="mini" > {{audioPlayer.paused ? '▶ 继续播放' : '⏸ 暂停'}} </button> <button class="btn-control btn-close" bindtap="closeAudioPlayer" size="mini" > 关闭 </button> </view> <!-- 播放状态提示 --> <view class="voice-status"> <text wx:if="{{audioPlayer.playing}}">▶ 正在播放语音讲解...</text> <text wx:if="{{audioPlayer.paused}}">⏸ 语音已暂停</text> </view> </view> <!-- 无语音提示 (有图片或文字但无语音时显示) --> <view wx:if="{{!audioPlayer.hasVoice && (audioPlayer.hasImage || audioPlayer.answerText)}}" class="no-voice-section"> <view class="no-voice-tip"> <text>🎵 暂无语音讲解,请阅读上方文字介绍</text> </view> <button class="btn-control btn-close-full" bindtap="closeAudioPlayer" > 关闭讲解 </button> </view> </view> </view> </view> </view> // services/map-service.js // 地图显示与景点数据服务 (单例模式) const utils = require('../utils/index'); // 默认地图配置 - 当后端配置获取失败时使用 const DEFAULT_MAP_CONFIG = { activeMode: 'tencent', handdraw: { enabled: false, imageUrl: '', bounds: { north: null, south: null, east: null, west: null } }, markerIcons: { default: 'https://your-bucket.cos.ap-shanghai.myqcloud.com/icons/spot_handdraw.png' } }; // 保底常量 - 当所有配置都失效时使用 const FALLBACK_CONSTANTS = { TENCENT_MAP_FALLBACK: true, DEFAULT_MARKER_ICON: '/images/default-marker.png' }; const mapService = { /** * 地图标记点点击事件 * @param {Object} e - 事件对象 * @param {Object} page - 页面实例 */ onMapMarkerTap: function (e, page) { const markerId = e.markerId; console.log('【地图页】标记点被点击,id:', markerId); const app = getApp(); if (!app.isInteractionAllowed()) { wx.showToast({ title: '伴游模式运行中,此功能不可用', icon: 'none' }); return; } if (page.data.currentMode !== 'exclusive' || page.isButtonDebouncing()) return; const spot = page.data.mapSpots.find(s => s.id === markerId); if (!spot) return; page.setData({ selectedSpot: spot, showSpotActionSheet: true }); }, /** * 地图景点组件点击事件 * @param {Object} e - 事件对象 * @param {Object} page - 页面实例 */ onMapSpotTap: function (e, page) { const app = getApp(); if (!app.isInteractionAllowed()) { wx.showToast({ title: '伴游模式运行中,此功能不可用', icon: 'none' }); return; } if (page.data.currentMode !== 'exclusive' || page.isButtonDebouncing()) return; const spotId = e.currentTarget.dataset.spotId; const spot = page.data.mapSpots.find(s => s.id === spotId); if (!spot) return; page.setData({ selectedSpot: spot, showSpotActionSheet: true }); }, /** * 获取地图配置 * @param {Object} page - 页面实例 * @returns {Promise<Object>} 地图配置对象 */ async getMapConfig(page) { const app = getApp(); try { const res = await app.callGateway('getMapConfig', {}); console.log('【getMapConfig-调试】原始接口响应:', res); console.log('【getMapConfig-调试】响应code:', res.code); console.log('【getMapConfig-调试】响应data:', res.data); console.log('【getMapConfig-调试】响应data.markerIcons:', res.data?.markerIcons); console.log('【getMapConfig-调试】响应data.markerIcons.default:', res.data?.markerIcons?.default); if (res.code === 200) { console.log('✅ 地图配置获取成功:', res.data); return res.data; } else { console.warn('获取地图配置返回非200状态码:', res.code, res.message); } } catch (err) { console.warn('获取地图配置失败:', err); } console.log('【getMapConfig-调试】使用默认地图配置:', DEFAULT_MAP_CONFIG); return DEFAULT_MAP_CONFIG; }, /** * 判断是否应使用手绘地图 * @param {Object} config - 地图配置 * @returns {boolean} 是否使用手绘地图 */ shouldUseHanddrawMap(config) { // 必须满足所有条件 return config.activeMode === 'handdraw' && config.handdraw?.enabled === true && config.handdraw?.imageUrl && config.handdraw?.imageUrl.trim() && this.hasValidBounds(config.handdraw.bounds); }, /** * 检查边界是否有效 * @param {Object} bounds - 边界对象 * @returns {boolean} 边界是否有效 */ hasValidBounds(bounds) { if (!bounds) return false; const { north, south, east, west } = bounds; return north !== null && south !== null && east !== null && west !== null && north > south && east > west; // 基本地理合理性检查 }, /** * 【核心修改】尝试加载手绘地图 * @param {Object} handdrawConfig - 手绘地图配置 * @param {Object} page - 页面实例 * @returns {Promise<boolean>} 是否加载成功 */ tryLoadHanddrawMap: function(handdrawConfig, page) { // 【新增调试】记录开始 page._addDebugLog && page._addDebugLog(`[服务] tryLoadHanddrawMap 被调用,配置: ${JSON.stringify(handdrawConfig)}`); console.log('🔄 开始加载手绘地图为groundOverlays'); if (!handdrawConfig || !handdrawConfig.imageUrl || !handdrawConfig.bounds) { const errorMsg = '❌ 手绘地图配置无效'; page._addDebugLog && page._addDebugLog(`[服务] ${errorMsg}`); console.warn(errorMsg); return false; } const bounds = handdrawConfig.bounds; // 【新增】详细记录配置 page._addDebugLog && page._addDebugLog(`[服务] 图片URL: ${handdrawConfig.imageUrl}, 边界: ${JSON.stringify(bounds)}`); // 【关键】验证边界数值 if (!this.hasValidBounds(bounds)) { const errorMsg = '❌ 手绘地图边界数据无效,无法创建覆盖物'; page._addDebugLog && page._addDebugLog(`[服务] ${errorMsg}`); console.error(errorMsg); return false; } const groundOverlay = { id: 0, // 必须为数字 src: handdrawConfig.imageUrl, bounds: { southwest: { latitude: parseFloat(bounds.south), // 确保是数字 longitude: parseFloat(bounds.west) }, northeast: { latitude: parseFloat(bounds.north), longitude: parseFloat(bounds.east) } }, opacity: 1.0, // 必须是1.0,不是1 zIndex: 9999 }; page._addDebugLog && page._addDebugLog(`[服务] 构造的覆盖物对象: ${JSON.stringify(groundOverlay)}`); console.log('✅ 创建groundOverlay对象:', groundOverlay); // 【核心逻辑】检查地图组件就绪状态,决定是直接应用还是加入队列 if (page.data.debug && page.data.debug.isMapComponentReady) { page._addDebugLog && page._addDebugLog(`[服务] 地图组件已就绪,立即应用覆盖物。`); this._applyGroundOverlayWithLog(groundOverlay, page, handdrawConfig); } else { page._addDebugLog && page._addDebugLog(`[服务] 地图组件未就绪,将覆盖物加入等待队列。`); // 存入队列,等待地图就绪事件处理 const currentQueue = page.data.debug.groundOverlaysQueue || []; page.setData({ 'debug.groundOverlaysQueue': [groundOverlay, ...currentQueue].slice(0, 5), // 队列只保留最新的5个 handdrawConfig: handdrawConfig }); } return true; }, /** * 【新增函数】带详细日志的覆盖物应用函数 */ _applyGroundOverlayWithLog: function(groundOverlay, page, handdrawConfig) { const applyStartTime = Date.now(); page._addDebugLog && page._addDebugLog(`[服务] 开始应用覆盖物到地图...`); // 先清空现有覆盖物 page.setData({ groundOverlays: [] }, () => { page._addDebugLog && page._addDebugLog(`[服务] 已清空现有覆盖物。`); // 短暂延迟后设置新覆盖物 setTimeout(() => { page.setData({ groundOverlays: [groundOverlay], handdrawConfig: handdrawConfig, 'debug.lastGroundOverlaySetTime': Date.now() }, () => { const applyEndTime = Date.now(); page._addDebugLog && page._addDebugLog(`[服务] ✅ 覆盖物已设置到页面Data,耗时 ${applyEndTime - applyStartTime}ms。`); page._addDebugLog && page._addDebugLog(`[服务] 设置后 groundOverlays: ${JSON.stringify(page.data.groundOverlays)}`); // 调整地图视野到覆盖物中心 this._adjustMapToShowOverlayWithLog(page, groundOverlay); // 尝试强制重绘 this.forceMapRedraw(page); }); }, 150); // 清空和设置之间的延迟 }); }, /** * 【新增函数】带日志的地图视野调整 */ _adjustMapToShowOverlayWithLog: function(page, overlay) { if (!overlay || !overlay.bounds) { page._addDebugLog && page._addDebugLog(`[服务] ❌ 无法调整视野:覆盖物或边界无效。`); return; } const bounds = overlay.bounds; const centerLat = (bounds.southwest.latitude + bounds.northeast.latitude) / 2; const centerLng = (bounds.southwest.longitude + bounds.northeast.longitude) / 2; const latRange = bounds.northeast.latitude - bounds.southwest.latitude; const lngRange = bounds.northeast.longitude - bounds.southwest.longitude; page._addDebugLog && page._addDebugLog(`[服务] 覆盖物中心计算: (${centerLat}, ${centerLng}), 纬度范围: ${latRange}, 经度范围: ${lngRange}`); // 计算合适的缩放级别 const maxRange = Math.max(latRange, lngRange); let zoomLevel = 15; // 默认 if (maxRange < 0.001) zoomLevel = 19; else if (maxRange < 0.005) zoomLevel = 18; else if (maxRange < 0.01) zoomLevel = 17; else if (maxRange < 0.05) zoomLevel = 16; else if (maxRange < 0.1) zoomLevel = 15; else if (maxRange < 0.2) zoomLevel = 14; else zoomLevel = 13; page.setData({ mapCenterLatitude: centerLat, mapCenterLongitude: centerLng, mapScale: zoomLevel }, () => { page._addDebugLog && page._addDebugLog(`[服务] 地图视野已调整至中心(${centerLat}, ${centerLng}),缩放等级 ${zoomLevel}`); }); }, /** * 【新增函数】强制地图重绘 */ forceMapRedraw: function(page) { console.log('🔄 强制地图重绘groundOverlays'); page._addDebugLog && page._addDebugLog(`[服务] 强制地图重绘被调用`); if (!page || !page.data) { console.error('❌ page对象无效'); page._addDebugLog && page._addDebugLog(`[服务] ❌ page对象无效,无法重绘`); return; } const originalScale = page.data.mapScale || 18; const originalLat = page.data.mapCenterLatitude; const originalLng = page.data.mapCenterLongitude; if (!originalLat || !originalLng) { console.warn('❌ 地图中心坐标无效,无法重绘'); page._addDebugLog && page._addDebugLog(`[服务] ❌ 地图中心坐标无效,无法重绘`); return; } // 方法1:通过改变地图参数触发重绘 page.setData({ mapScale: originalScale + 0.001 }, () => { // 立即改回 setTimeout(() => { page.setData({ mapScale: originalScale }, () => { console.log('✅ 地图缩放重绘完成'); page._addDebugLog && page._addDebugLog(`[服务] ✅ 地图缩放重绘完成`); // 方法2:通过地图上下文再次触发 this.triggerMapContextRedraw(page, originalLat, originalLng); }); }, 50); }); }, /** * 【新增函数】通过地图上下文触发重绘 */ triggerMapContextRedraw: function(page, lat, lng) { try { const mapCtx = wx.createMapContext('tencentMap', page); if (mapCtx && mapCtx.moveToLocation) { console.log('🔄 通过地图上下文强制重绘'); page._addDebugLog && page._addDebugLog(`[服务] 通过地图上下文强制重绘`); // 先移动到旁边 mapCtx.moveToLocation({ latitude: lat + 0.00001, longitude: lng }); // 延迟后再移回来 setTimeout(() => { mapCtx.moveToLocation({ latitude: lat, longitude: lng }); console.log('✅ 地图上下文重绘完成'); page._addDebugLog && page._addDebugLog(`[服务] ✅ 地图上下文重绘完成`); }, 100); } else { console.warn('⚠️ 地图上下文创建失败'); page._addDebugLog && page._addDebugLog(`[服务] ⚠️ 地图上下文创建失败`); } } catch (err) { console.error('❌ 地图上下文操作失败:', err); page._addDebugLog && page._addDebugLog(`[服务] ❌ 地图上下文操作失败: ${err.message}`); } }, /** * 降级到腾讯地图 * @param {Object} page - 页面实例 * @param {string} reason - 降级原因 */ fallbackToTencentMap(page, reason) { console.log(`【地图降级】原因: ${reason}`); page.setData({ usingTencentMap: true, groundOverlays: [] // 清除地面覆盖物 }); // 只显示一次提示,避免频繁打扰 if (!page.data.hasShownFallbackToast) { wx.showToast({ title: '已使用腾讯地图', icon: 'none', duration: 1500 }); page.setData({ hasShownFallbackToast: true }); } }, /** * 获取标点图标路径(多级降级策略) * @param {Object} mapConfig - 地图配置 * @returns {string} 标点图标路径 */ getMarkerIconPath(mapConfig) { console.log('【getMarkerIconPath-调试】开始图标决策'); console.log('【getMarkerIconPath-调试】输入mapConfig:', mapConfig); console.log('【getMarkerIconPath-调试】输入mapConfig.markerIcons:', mapConfig?.markerIcons); console.log('【getMarkerIconPath-调试】输入mapConfig.markerIcons.default:', mapConfig?.markerIcons?.default); console.log('【getMarkerIconPath-调试】DEFAULT_MAP_CONFIG.markerIcons.default:', DEFAULT_MAP_CONFIG.markerIcons.default); console.log('【getMarkerIconPath-调试】FALLBACK_CONSTANTS.DEFAULT_MARKER_ICON:', FALLBACK_CONSTANTS.DEFAULT_MARKER_ICON); // 1. 优先使用配置中的图标 if (mapConfig?.markerIcons?.default) { console.log('✅ 【getMarkerIconPath-决策】使用配置中的图标:', mapConfig.markerIcons.default); return mapConfig.markerIcons.default; } else { console.log('❌ 【getMarkerIconPath-决策】配置中没有图标,尝试默认配置'); } // 2. 使用默认配置的图标 if (DEFAULT_MAP_CONFIG.markerIcons.default) { console.log('✅ 【getMarkerIconPath-决策】使用默认配置图标:', DEFAULT_MAP_CONFIG.markerIcons.default); return DEFAULT_MAP_CONFIG.markerIcons.default; } else { console.log('❌ 【getMarkerIconPath-决策】默认配置中也没有图标,尝试保底图标'); } // 3. 使用保底本地图标 console.log('✅ 【getMarkerIconPath-决策】使用保底本地图标:', FALLBACK_CONSTANTS.DEFAULT_MARKER_ICON); return FALLBACK_CONSTANTS.DEFAULT_MARKER_ICON; }, /** * 坐标转换计算 * @param {number} lng - 经度 * @param {number} lat - 纬度 * @param {Object} bounds - 边界 * @param {Object} imageSize - 图片尺寸 * @returns {Object|null} 像素坐标 */ calculatePixelPosition(lng, lat, bounds, imageSize) { const { north, south, east, west } = bounds; const { width, height } = imageSize; // 检查是否在地图范围内 if (lng < west || lng > east || lat < south || lat > north) { console.warn(`坐标(${lng}, ${lat})不在手绘地图范围内`); return null; } // 线性映射 const x = ((lng - west) / (east - west)) * width; const y = ((north - lat) / (north - south)) * height; return { x, y }; }, /** * 将标点坐标转换为手绘地图像素坐标 * @param {Object} page - 页面实例 */ convertSpotsToHanddraw(page) { const { mapSpots, handdrawConfig, mapImageSize } = page.data; if (!mapSpots || !handdrawConfig || !mapImageSize.width) { console.warn('坐标转换条件不满足'); return; } const bounds = handdrawConfig.bounds; const imageSize = mapImageSize; // 验证边界有效性 if (!this.hasValidBounds(bounds)) { console.warn('手绘地图边界无效,无法转换坐标'); return; } const handdrawSpots = mapSpots.map(spot => { const pixelPos = this.calculatePixelPosition( spot.longitude, spot.latitude, bounds, imageSize ); if (pixelPos) { return { ...spot, pixelX: pixelPos.x, pixelY: pixelPos.y }; } return null; }).filter(spot => spot !== null); page.setData({ handdrawSpots }); console.log(`坐标转换完成,有效标点: ${handdrawSpots.length}/${mapSpots.length}`); }, /** * 加载地图和附近景点 * @param {number} centerLat - 中心纬度 * @param {number} centerLng - 中心经度 * @param {Object} page - 页面实例 */ async loadMapAndNearbySpots(centerLat, centerLng, page) { const app = getApp(); page.showLoading('加载地图资源中...'); try { console.log('【loadMapAndNearbySpots-调试】开始加载地图配置'); // 1. 获取地图配置 let mapConfig = DEFAULT_MAP_CONFIG; try { const configRes = await this.getMapConfig(page); console.log('【loadMapAndNearbySpots-调试】getMapConfig返回结果:', configRes); console.log('【loadMapAndNearbySpots-调试】configRes.activeMode:', configRes?.activeMode); if (configRes && configRes.activeMode) { mapConfig = { ...DEFAULT_MAP_CONFIG, ...configRes }; console.log('✅ 使用后端地图配置'); console.log('【loadMapAndNearbySpots-调试】合并后的mapConfig:', mapConfig); console.log('【loadMapAndNearbySpots-调试】合并后的mapConfig.markerIcons:', mapConfig.markerIcons); console.log('【loadMapAndNearbySpots-调试】合并后的mapConfig.markerIcons.default:', mapConfig.markerIcons.default); } else { console.warn('后端配置无效,使用默认配置'); } } catch (err) { console.warn('获取地图配置失败,使用默认配置:', err); } // 保存配置到页面 page.setData({ mapConfig }); console.log('【loadMapAndNearbySpots-调试】已保存mapConfig到页面data'); // 2. 决策地图模式 const shouldUseHanddraw = this.shouldUseHanddrawMap(mapConfig); console.log('【loadMapAndNearbySpots-调试】是否应使用手绘地图:', shouldUseHanddraw); console.log('【loadMapAndNearbySpots-调试】mapConfig.activeMode:', mapConfig.activeMode); console.log('【loadMapAndNearbySpots-调试】mapConfig.handdraw?.enabled:', mapConfig.handdraw?.enabled); console.log('【loadMapAndNearbySpots-调试】mapConfig.handdraw?.imageUrl:', mapConfig.handdraw?.imageUrl); console.log('【loadMapAndNearbySpots-调试】hasValidBounds:', this.hasValidBounds(mapConfig.handdraw?.bounds)); if (shouldUseHanddraw) { // 3. 尝试手绘地图模式(作为地面覆盖物) console.log('【loadMapAndNearbySpots-调试】尝试加载手绘地图为覆盖物'); const handdrawSuccess = await this.tryLoadHanddrawMap(mapConfig.handdraw, page); if (!handdrawSuccess) { // 手绘地图失败,降级到标准腾讯地图 console.warn('手绘地图覆盖物加载失败,使用标准腾讯地图'); this.fallbackToTencentMap(page, '手绘地图加载失败'); // 设置地图中心为用户位置 page.setData({ mapCenterLatitude: centerLat, mapCenterLongitude: centerLng, mapScale: 18 }); } else { console.log('✅ 手绘地图覆盖物加载成功'); } } else { // 4. 使用腾讯地图(默认或配置指定) console.log('使用标准腾讯地图模式'); this.fallbackToTencentMap(page, '配置为腾讯地图模式'); // 设置地图中心为用户位置 page.setData({ mapCenterLatitude: centerLat, mapCenterLongitude: centerLng, mapScale: 18 }); } // 5. 加载景点数据 console.log('【loadMapAndNearbySpots-调试】开始加载景点数据'); await this.loadNearbySpotsFromCloud(centerLat, centerLng, page, mapConfig); } catch (err) { console.error('地图加载异常,强制使用腾讯地图:', err); this.fallbackToTencentMap(page, '加载异常'); // 设置地图中心为用户位置 page.setData({ mapCenterLatitude: centerLat, mapCenterLongitude: centerLng, mapScale: 18 }); await this.loadNearbySpotsFromCloud(centerLat, centerLng, page, DEFAULT_MAP_CONFIG); } finally { page.hideLoading(); } }, /** * 使用默认位置 * @param {Object} page - 页面实例 */ useDefaultLocation: function (page) { const defaultLat = 30.8; const defaultLng = 120.3; page.setData({ mapCenterLatitude: defaultLat, mapCenterLongitude: defaultLng }); this.loadMapAndNearbySpots(defaultLat, defaultLng, page); wx.showToast({ title: '已使用默认位置,部分功能受限', icon: 'none', duration: 2000 }); }, /** * 解析位置字符串 (从原始 map.js 中迁移的工具函数) * @param {*} locationObj - 位置对象 * @returns {Object} 解析结果 */ parseLocationString: function(locationObj) { // 这里直接调用已提取到 utils 中的函数 // 注意:此函数是工具函数,不应直接操作 page,因此不传入 page 参数 // 实际实现应已在 utils/content-utils.js 中 // 此处仅为保持函数签名一致,实际调用 utils 中的版本 return utils.parseLocationString(locationObj); }, /** * 从云端加载附近景点 * @param {number} centerLat - 中心纬度 * @param {number} centerLng - 中心经度 * @param {Object} page - 页面实例 * @param {Object} mapConfig - 地图配置 */ loadNearbySpotsFromCloud: function (centerLat, centerLng, page, mapConfig) { const app = getApp(); console.log('【地图页-调试】开始以用户位置为中心加载附近景点,中心点:', centerLat, centerLng); console.log('【loadNearbySpotsFromCloud-调试】传入的mapConfig:', mapConfig); console.log('【loadNearbySpotsFromCloud-调试】传入的mapConfig.markerIcons:', mapConfig?.markerIcons); console.log('【loadNearbySpotsFromCloud-调试】传入的mapConfig.markerIcons.default:', mapConfig?.markerIcons?.default); app.callGateway('getMapSpots', { centerLatitude: centerLat, centerLongitude: centerLng, }) .then(res => { console.log('✅ 附近景点数据加载结果:', res); if (res.code === 200) { const spotsData = Array.isArray(res.data) ? res.data : []; console.log('【地图页-调试】转换后景点数组 spotsData:', spotsData); console.log('【地图页-调试】数组长度:', spotsData.length); if (spotsData.length > 0) { console.log(`✅ 加载云端景点数据成功,数量: ${spotsData.length}`); // 获取zhu合集图标路径(第二优先级) const globalDefaultIcon = this.getMarkerIconPath(mapConfig || DEFAULT_MAP_CONFIG); console.log('【图标优先级-全局】zhu合集图标路径:', globalDefaultIcon); const spots = spotsData.map((item, index) => { let finalLatitude = null; let finalLongitude = null; if (item.latitude !== undefined && item.longitude !== undefined) { finalLatitude = parseFloat(item.latitude); finalLongitude = parseFloat(item.longitude); console.log(`【前端解析】使用后端解析的坐标: ${finalLatitude}, ${finalLongitude}`); } else if (item.location) { const parsed = this.parseLocationString(item.location); if (parsed.success) { finalLatitude = parsed.latitude; finalLongitude = parsed.longitude; } else { console.warn(`❌ 【前端解析】景点"${item.name}"的location解析失败`); return null; } } else { console.warn(`❌ 【前端解析】景点"${item.name}"无location信息`); return null; } if (finalLatitude === null || finalLongitude === null || isNaN(finalLatitude) || isNaN(finalLongitude) || Math.abs(finalLatitude) > 90 || Math.abs(finalLongitude) > 180) { console.warn(`❌ 【前端解析】景点"${item.name}"坐标无效: ${finalLatitude}, ${finalLongitude}`); return null; } // ========== 【核心修改】图标优先级逻辑 ========== // 替换原有的图标优先级判断逻辑 let finalIconPath = ''; const spotIcon = (item.iconUrl || item.icon || '').toString().trim(); if (spotIcon) { finalIconPath = spotIcon; console.log(`✅ 【图标优先级】景点"${item.name}"使用自身图标: ${finalIconPath}`); } else if (globalDefaultIcon && globalDefaultIcon.trim()) { finalIconPath = globalDefaultIcon.trim(); console.log(`✅ 【图标优先级】景点"${item.name}"使用zhu合集图标: ${finalIconPath}`); } else { finalIconPath = FALLBACK_CONSTANTS.DEFAULT_MARKER_ICON; console.log(`⚠️ 【图标优先级】景点"${item.name}"使用保底本地图标: ${finalIconPath}`); } console.log(`【图标调试】景点"${item.name}"最终图标: ${finalIconPath}`); // ========== 【核心修改结束】 ========== return { id: item._id || index, name: item.name || `景点_${item._id || index}`, latitude: finalLatitude, longitude: finalLongitude, width: 40, height: 40, iconPath: finalIconPath, // 使用最终确定的图标路径 rawData: item }; }).filter(spot => spot !== null); console.log('【地图页-调试】转换后的地图标点数组 (mapSpots):', spots); console.log(`✅ 【前端解析】成功解析 ${spots.length}/${spotsData.length} 个景点`); if (spots.length > 0) { page.setData({ mapSpots: spots }); console.log('【loadNearbySpotsFromCloud-调试】已设置mapSpots到页面data'); console.log('【loadNearbySpotsFromCloud-调试】第一个标点的iconPath:', spots[0]?.iconPath); // 注意:现在手绘地图是覆盖物,不需要转换坐标 // 景点会直接显示在腾讯地图上,手绘地图图片会覆盖在指定区域 } else { console.warn('❌ 所有景点数据解析失败,地图无标点'); page.setData({ mapSpots: [] }); wx.showToast({ title: '景点坐标解析失败,请检查数据格式', icon: 'none', duration: 3000 }); } } else { console.warn('云端返回的景点数据为空'); page.setData({ mapSpots: [] }); wx.showToast({ title: '当前景区暂无景点数据', icon: 'none' }); } } else { console.error('加载云端景点数据失败,状态码非200:', res.message || '未知错误'); page.setData({ mapSpots: [] }); wx.showToast({ title: '加载景点数据失败,请稍后重试', icon: 'none' }); } }).catch(err => { console.error('调用云端接口失败:', err); page.setData({ mapSpots: [] }); wx.showToast({ title: '网络请求失败', icon: 'none' }); }).finally(() => { page.hideLoading(); }); } }; // 导出单例 module.exports = mapService;
刚刚申请回来的域名,然后在微信环境直接是被封的状态,业务什么都没上线,也不会存在有违规情况!很奇怪,麻烦官方关注下,给于指引 地址是这个 :https://pp9.top/
[图片] 我们是一个探鲸·达人通告聚合平台。 应官方要求接入虚拟支付,但是目前不知道这个商户号当时是怎么来的了,怎么申请的, 账号是多少,密码是多少。还是说这个商户号的入口就是在小程序的虚拟支付里面,并不能像之前的微信商户号有一个单独的入口? 目前小程序的这个订单查看,金额查看,收益查看的界面实在是看起来不方便,对账不方便,所以想咨询一下虚拟商户的商户号是否还有其他的后台可以查看。