// pages/ai-plan/ai-plan.js const app = getApp(); const AMapKey = 'ad2afc314b741d5520274e419fda8655'; // 请替换为实际的高德地图Key Page({ data: { currentStep: 1, planGenerated: false, selectedDays: 3, selectedCity: '', selectedInterests: ['patriotism'], isRegenerating: false, planData: null, selectedTransport: 'public', customRequirements: '', dayOptions: [ { label: '1天', value: 1 }, { label: '2天', value: 2 }, { label: '3天', value: 3 }, { label: '4天', value: 4 }, { label: '5天', value: 5 }, { label: '6天', value: 6 }, ], cityOptions: [], interestOptions: [ { label: '革命历史', value: 'history' }, { label: '红色教育', value: 'education' }, { label: '党史学习', value: 'party-history' }, { label: '爱国主义', value: 'patriotism' } ], transportOptions: [ { label: '公共交通', value: 'public', icon: '/images/icons/bus.png' }, { label: '自驾', value: 'drive', icon: '/images/icons/car.png' }, { label: '步行', value: 'walk', icon: '/images/icons/walk.png' }, { label: '骑行', value: 'bike', icon: '/images/icons/bike.png' } ], isLoading: false, apiStatus: { citiesLoaded: false, attractionsLoaded: false }, showAttractionDetail: false, currentAttraction: null, attractionDetail: null, lastGeneratedAt: null, isRegenerated: false, mapData: { markers: [], polyline: [], latitude: 39.9042, longitude: 116.4074, scale: 12, showMap: false, includePoints: [] }, currentMapDay: 0, mapLoaded: false, isFavorite: false, favoriteLoading: false, }, onLoad() { this.initPage(); this.loadInitialData(); this.initMapControls(); // 检查是否有需要恢复的状态 const pageState = wx.getStorageSync('aiPlanPageState'); if (pageState && wx.getStorageSync('token')) { // 如果有保存的状态且用户已登录,恢复状态 this.restorePageState(); } }, onShow() { // 页面显示时检查是否是从登录页面返回 const token = wx.getStorageSync('token'); const pageState = wx.getStorageSync('aiPlanPageState'); if (token && pageState) { // 用户已登录且有保存的状态,恢复状态并继续操作 this.restorePageState(); } }, initPage() { const date = new Date(); this.setData({ currentDate: `${date.getMonth() + 1}月${date.getDate()}日` }); this.initDefaultImages(); }, initDefaultImages() { this.setData({ transportOptions: this.data.transportOptions.map(item => ({ ...item, icon: this.checkImageExists(item.icon) ? item.icon : '/images/default-transport.png' })) }); }, loadInitialData() { this.getCities(); }, // 获取城市列表 getCities() { this.setData({ isLoading: true, cityOptions: [] }); wx.request({ url: app.globalData.apiBaseUrl + '/api/cities/', method: 'GET', success: (res) => { if (res.statusCode === 200) { const rawData = res.data.data || res.data; const cityOptions = rawData.map(city => ({ label: city.name || '未知城市', value: String(city.id || '0'), disabled: city.is_available === false, location: { latitude: city.latitude || 39.9042, longitude: city.longitude || 116.4074 } })); this.setData({ cityOptions, 'apiStatus.citiesLoaded': true }); } else { this.handleApiError(res); } }, fail: this.handleNetworkError, complete: () => { this.setData({ isLoading: false }); } }); }, // 检查图片是否存在 checkImageExists(path) { try { const fs = wx.getFileSystemManager(); return fs.accessSync(path) === undefined; } catch (e) { return false; } }, // 从API获取景点图片 fetchAttractionImage(attraction) { return new Promise((resolve, reject) => { // 获取城市名称 let cityName = '济南'; // 默认值 if (attraction.city_id && this.data.cityOptions) { const city = this.data.cityOptions.find(c => c.value === attraction.city_id); if (city) cityName = city.label.replace(/市$/, ''); } wx.request({ url: `${app.globalData.apiBaseUrl}/api/attractions/image/`, method: 'GET', data: { name: attraction.name, city: cityName }, success: (res) => { if (res.data.status === 'success' && res.data.image_url) { // 检查URL是否完整,如果不完整则补全 const imageUrl = res.data.image_url.startsWith('http') ? res.data.image_url : `${app.globalData.mediaBaseUrl}${res.data.image_url}`; resolve(imageUrl); } else { reject(new Error('未获取到有效图片')); } }, fail: (err) => { reject(err); } }); }); }, // 处理图片加载错误 handleImageError(e) { const { id, name, city } = e.currentTarget.dataset; console.log('图片加载失败:', name, id); // 设置加载状态 this.setData({ 'currentAttraction.isLoadingImage': true }); // 异步调用API获取新图片 this.fetchAttractionImage({ id: id, name: name, city_id: city }).then(imageUrl => { this.updateAttractionImage(id, name, imageUrl); }).catch(err => { console.error('获取图片失败:', err); this.setData({ 'currentAttraction.image': '/images/image-load-failed.jpg', 'currentAttraction.isLoadingImage': false }); }); }, // 更新景点图片 updateAttractionImage(id, name, newImageUrl) { // 更新当前景点图片 if (this.data.currentAttraction && (this.data.currentAttraction.id === id || this.data.currentAttraction.name === name)) { this.setData({ 'currentAttraction.image': newImageUrl, 'currentAttraction.isLoadingImage': false }); } // 更新planData中的对应景点图片 if (this.data.planData && this.data.planData.days) { const updatedPlan = JSON.parse(JSON.stringify(this.data.planData)); let updated = false; for (const day of updatedPlan.days) { for (const attraction of day.attractions) { if ((id && attraction.id === id) || (name && attraction.name === name)) { attraction.image = newImageUrl; updated = true; break; } } if (updated) break; } if (updated) { this.setData({ planData: updatedPlan }); } } }, // 图片加载成功处理 handleImageLoad(e) { console.log('图片加载成功', e.currentTarget.dataset.name); this.setData({ 'currentAttraction.isLoadingImage': false }); }, // 选择天数 selectDays(e) { this.setData({ selectedDays: e.currentTarget.dataset.value }); }, // 切换城市选择 toggleCity(e) { const value = String(e.currentTarget.dataset.value); this.setData({ selectedCity: this.data.selectedCity === value ? '' : value }); }, // 切换兴趣爱好 toggleInterest(e) { const value = e.currentTarget.dataset.value; const index = this.data.selectedInterests.indexOf(value); let newInterests = [...this.data.selectedInterests]; if (index === -1) { newInterests.push(value); } else { newInterests.splice(index, 1); } this.setData({ selectedInterests: newInterests }); }, // 选择交通方式 selectTransport(e) { this.setData({ selectedTransport: e.currentTarget.dataset.value }); }, // 更新自定义需求 updateCustomRequirements(e) { this.setData({ customRequirements: e.detail.value }); }, // 验证输入 validateInputs() { if (!this.data.selectedCity) { wx.showToast({ title: '请至少选择一个城市', icon: 'none' }); return false; } if (this.data.selectedDays < 1) { wx.showToast({ title: '请选择有效天数', icon: 'none' }); return false; } return true; }, // 下一步 goToNextStep() { if (this.data.currentStep === 1) { if (!this.validateInputs()) return; // 检查登录状态 const token = wx.getStorageSync('token'); if (!token) { // 保存当前页面状态,用于登录后恢复 this.savePageState(); // 跳转到登录页并携带当前页面路径和参数 wx.navigateTo({ url: `/pages/login/login?redirect=${encodeURIComponent('/pages/ai-plan/ai-plan')}&action=generate` }); return; } this.setData({ currentStep: 2 }); this.generatePlanWithAI(); } else if (this.data.currentStep === 2) { this.setData({ currentStep: 3 }); } }, // 保存页面状态 savePageState() { const pageState = { selectedDays: this.data.selectedDays, selectedCity: this.data.selectedCity, selectedInterests: this.data.selectedInterests, selectedTransport: this.data.selectedTransport, customRequirements: this.data.customRequirements, currentStep: this.data.currentStep }; wx.setStorageSync('aiPlanPageState', pageState); }, // 恢复页面状态 restorePageState() { const pageState = wx.getStorageSync('aiPlanPageState'); if (pageState) { this.setData({ selectedDays: pageState.selectedDays, selectedCity: pageState.selectedCity, selectedInterests: pageState.selectedInterests, selectedTransport: pageState.selectedTransport, customRequirements: pageState.customRequirements, currentStep: pageState.currentStep }); // 清除保存的状态 wx.removeStorageSync('aiPlanPageState'); // 如果是在第二步,继续生成计划 if (pageState.currentStep === 1) { this.setData({ currentStep: 2 }); this.generatePlanWithAI(); } } }, // 生成行程 generatePlanWithAI() { if (this.data.isLoading) return; const token = wx.getStorageSync('token'); console.log('token:', token); if (!token) { wx.showToast({ title: '请先登录', icon: 'none' }); // 保存当前页面状态 this.savePageState(); // 跳转到登录页并携带当前页面路径 wx.navigateTo({ url: `/pages/login/login?redirect=${encodeURIComponent('/pages/ai-plan/ai-plan')}` }); return; } if (!this.validateInputs()) return; this.setData({ isLoading: true, planGenerated: false, planData: null }); const requestData = { city_ids: [Number(this.data.selectedCity)], days: Number(this.data.selectedDays), interests: [...this.data.selectedInterests, 'red-tourism'], transport: this.data.selectedTransport, custom_requirements: this.data.customRequirements }; wx.request({ url: app.globalData.apiBaseUrl + '/api/red-tourism/plan/', method: 'POST', data: requestData, header: { 'Content-Type': 'application/json', 'Authorization': 'Token ' + wx.getStorageSync('token') }, success: (res) => { if (res.statusCode === 401) { // 处理token过期情况 wx.removeStorageSync('token'); wx.showToast({ title: '登录已过期,请重新登录', icon: 'none' }); wx.navigateTo({ url: '/pages/login/login' }); return; } console.log('API返回原始数据:', JSON.stringify(res.data)); if (res.data?.days?.[0]?.attractions?.[0]) { console.log('首个景点数据结构:', res.data.days[0].attractions[0]); } if (res.statusCode === 200) { if (res.data && res.data.status === 'success' && res.data.data) { this.processAndShowPlan(res.data.data); } else { this.handleError('返回数据格式不正确'); } } else { this.handleError(res.data?.message || `请求失败: ${res.statusCode}`); } }, fail: (err) => { if (err.statusCode === 401) { wx.showToast({ title: '登录已过期,请重新登录', icon: 'none' }); // 清除无效的 token wx.removeStorageSync('token'); // 跳转到登录页面 wx.navigateTo({ url: '/pages/login/login' }); } else { this.handleError('网络请求失败,请检查连接'); } }, complete: () => { wx.hideLoading(); this.setData({ isLoading: false }); } }); }, // 处理并显示行程数据 async processAndShowPlan(planData, isRegenerate = false) { try { // 获取所有景点的坐标 const processedPlan = await this.getAttractionsCoordinates(planData); // 标准化处理后的数据 const normalizedData = this.normalizePlanData(processedPlan); console.log('标准化后的数据:', JSON.stringify(normalizedData, null, 2)); // 处理地图数据 await this.processMapData(normalizedData.days); // 更新页面数据 this.setData({ planData: normalizedData, planGenerated: true, isLoading: false, lastGeneratedAt: new Date().toISOString(), isRegenerated: isRegenerate, mapLoaded: true, currentMapDay: -1 // 默认显示所有景点 }); } catch (error) { console.error('处理行程数据时出错:', error); this.handleError('处理行程数据时出错'); } }, // 通过高德地图API获取景点坐标 async getAttractionsCoordinates(planData) { const days = planData.days || []; const processedDays = []; for (const day of days) { const processedAttractions = []; for (const attraction of day.attractions) { try { // 如果已经有坐标则跳过 if (attraction.location && attraction.location.latitude && attraction.location.longitude) { processedAttractions.push(attraction); continue; } // 优先通过高德地图API获取坐标 let location; try { location = await this.getLocationFromAMap(attraction.name, attraction.address); console.log(`通过高德地图API获取到${attraction.name}的坐标:`, location); } catch (amapError) { console.warn(`高德地图API获取${attraction.name}坐标失败:`, amapError); // 如果高德地图API失败,检查API返回的原始坐标 if (attraction.latitude && attraction.longitude) { location = { latitude: parseFloat(attraction.latitude), longitude: parseFloat(attraction.longitude) }; console.log(`使用API返回的原始坐标:`, location); } else { // 如果都没有,使用默认坐标 throw new Error('无可用坐标数据'); } } // 更新景点坐标 processedAttractions.push({ ...attraction, location: { latitude: location.latitude, longitude: location.longitude } }); } catch (error) { console.error(`获取景点${attraction.name}坐标失败:`, error); // 使用默认坐标 processedAttractions.push({ ...attraction, location: { latitude: 39.9042, // 默认北京坐标 longitude: 116.4074 }, _coord_status: 'default' }); } } processedDays.push({ ...day, attractions: processedAttractions }); } return { ...planData, days: processedDays }; }, // 调用高德地图API获取坐标 getLocationFromAMap(name, address) { return new Promise((resolve, reject) => { // 构建查询参数 const keywords = encodeURIComponent(name); const city = address ? encodeURIComponent(address.split('市')[0] || '') : ''; wx.request({ url: `https://restapi.amap.com/v3/place/text?keywords=${keywords}&city=${city}&output=json&key=${AMapKey}`, method: 'GET', success: (res) => { if (res.data.status === '1' && res.data.pois && res.data.pois.length > 0) { const location = res.data.pois[0].location.split(','); resolve({ latitude: parseFloat(location[1]), longitude: parseFloat(location[0]), _coord_source: 'amap' }); } else { reject(new Error('未找到该景点的坐标信息')); } }, fail: (err) => { reject(new Error(`高德地图API请求失败: ${err.errMsg}`)); } }); }); }, // 标准化行程数据 normalizePlanData(planData) { // 默认行程模板 const DEFAULT_PLAN = { title: '红色文化之旅', description: '通过参观革命历史遗址学习党史', statistics: { red_attractions_count: 0, total_attractions: 0, educational_points_count: 0, total_hours: 0 }, red_tourism_tips: [ "请着装整洁,保持肃穆", "建议提前学习相关历史知识" ], suitable_for: "适合党员干部、学生团体等", days: [], education_goals: [ "传承红色基因", "学习革命历史", "培养爱国情怀" ] }; // 深度合并默认数据和API返回数据 const normalizedData = this.deepMerge(DEFAULT_PLAN, planData); // 处理每日行程 normalizedData.days = normalizedData.days.map((day, dayIndex) => { // 处理当天景点 const attractions = day.attractions.map((attr, attrIndex) => { // 生成唯一ID const attractionId = attr.id || `${dayIndex}-${attrIndex}`; // 处理图片路径 const cleanName = attr.name ? attr.name.replace(/[^\w\u4e00-\u9fa5]/g, '') : ''; const imageUrl = attr.image || `${app.globalData.mediaBaseUrl}/attractions/${encodeURIComponent(cleanName)}.png`; // 处理坐标 let location = { latitude: 36.6667, // 济南纬度 longitude: 117.0000 }; if (attr.location) { location = { latitude: parseFloat(attr.location.latitude) || location.latitude, longitude: parseFloat(attr.location.longitude) || location.longitude }; } else if (attr.latitude && attr.longitude) { location = { latitude: parseFloat(attr.latitude), longitude: parseFloat(attr.longitude) }; } // 返回标准化后的景点数据 return { id: attractionId, name: attr.name || '红色教育基地', description: attr.description || '红色教育基地,具有重要的历史教育意义', image: imageUrl, is_red_tourism: attr.is_red_tourism !== false, visit_time: attr.visit_time || this.generateVisitTime(attrIndex), duration: attr.duration || 120, address: attr.address || '地址信息待补充', open_hours: attr.open_hours || "09:00-17:00", ticket_info: attr.ticket_info || "凭身份证免费参观", history_significance: attr.history_significance || "革命历史重要遗址", location: location, educational_value: attr.educational_value || "高", visiting_etiquette: attr.visiting_etiquette || [ "请保持庄严肃穆", "勿大声喧哗", "纪念馆内请勿拍照" ] }; }); // 生成当日总结 const summary = day.summary || this.generateDaySummary( day.theme || '红色教育', attractions.filter(a => a.is_red_tourism) ); // 返回标准化后的每日数据 return { day: day.day || dayIndex + 1, theme: day.theme || '红色教育', transport: day.transport || this.data.selectedTransport || '公共交通', attractions: attractions, summary: summary, schedule: this.createDailySchedule(attractions, day.transport), travel_tips: day.travel_tips || [ "建议穿着舒适鞋子", "携带饮用水和防晒用品" ] }; }); // 计算统计数据 normalizedData.statistics = { red_attractions_count: normalizedData.days.reduce((count, day) => count + day.attractions.filter(a => a.is_red_tourism).length, 0), total_attractions: normalizedData.days.reduce((count, day) => count + day.attractions.length, 0), educational_points_count: normalizedData.days.reduce((count, day) => count + day.attractions.filter(a => a.is_red_tourism).length * 2, 0), total_hours: normalizedData.days.length * 8 // 每天按8小时计算 }; return normalizedData; }, // 辅助方法:获取图片处理统计信息 getImageResolutionStats(days) { const stats = { total: 0, resolved: 0, default_used: 0, missing_ids: [] }; days.forEach(day => { day.attractions.forEach(attraction => { stats.total++; if (attraction.image !== '/images/default-attraction.jpg') { stats.resolved++; } else { stats.default_used++; if (!attraction.id) stats.missing_ids.push('unknown'); } }); }); return stats; }, // 辅助方法:生成每日总结 generateDaySummary(theme, redAttractions) { const attractionNames = redAttractions.map(a => a.name).join('、'); return `今日主题为"${theme}",参观了${redAttractions.length}个红色教育基地${ attractionNames ? `,包括${attractionNames}` : '' }。通过实地参观学习,加深了对革命历史的理解和认识。`; }, // 辅助方法:创建每日行程安排 createDailySchedule(attractions, transport) { const timeSlots = ["09:00", "11:00", "14:00", "16:00"]; return attractions.map((att, index) => ({ time: timeSlots[index] || "10:00", duration: att.duration || 120, attraction: { id: att.id, name: att.name, image: att.image, is_red_tourism: att.is_red_tourism }, transport: index < attractions.length - 1 ? transport : null, transport_duration: "约30分钟", transport_cost: this.getTransportCost(transport), notes: [ `建议参观时间:${att.visit_time || '1-2小时'}`, att.ticket_info || '门票信息:免费' ] })); }, // 辅助方法:获取交通费用估算 getTransportCost(transportType) { const costs = { 'public': '约5-10元/人', 'drive': '油费约20-50元', 'walk': '免费', 'bike': '免费' }; return costs[transportType] || '费用待定'; }, // 辅助方法:生成参观时间 generateVisitTime(index) { const times = ["09:00-11:00", "11:00-13:00", "14:00-16:00", "16:00-18:00"]; return times[index] || "10:00-12:00"; }, // 辅助方法:深度合并对象 deepMerge(target, source) { if (typeof source !== 'object' || source === null) { return target; } for (const key in source) { if (source.hasOwnProperty(key)) { if (typeof source[key] === 'object' && source[key] !== null && !Array.isArray(source[key])) { if (!target[key]) { target[key] = {}; } this.deepMerge(target[key], source[key]); } else { target[key] = source[key]; } } } return target; }, // 处理地图数据 processMapData(days, selectedDay = -1) { if (!days || !days.length) { console.warn('没有有效的行程数据'); return; } // 定义不同天数的颜色 const dayColors = [ '#e54d42', // 红色 - 第一天 '#f37b1d', // 橙色 - 第二天 '#1cbbb4', // 青色 - 第三天 '#0081ff', // 蓝色 - 第四天 '#6739b6', // 紫色 - 第五天 '#9c26b0', // 紫红色 - 第六天 '#e03997' // 粉红色 - 第七天 ]; // 准备地图数据 const markers = []; const polylines = []; const includePoints = []; // 处理每一天的景点 days.forEach((day, dayIndex) => { // 如果指定了天数且不是当前天数,则跳过 if (selectedDay !== -1 && dayIndex !== selectedDay) return; const dayColor = dayColors[dayIndex % dayColors.length]; const dayPoints = []; // 处理当天景点 day.attractions.forEach((attraction, attrIndex) => { const markerId = dayIndex * 100 + attrIndex; // 创建标记点 markers.push({ id: markerId, latitude: attraction.location.latitude, longitude: attraction.location.longitude, iconPath: '/images/marker-default.png', width: 24, height: 24, callout: { content: `第${day.day}天 ${attrIndex + 1}. ${attraction.name}`, color: '#333', fontSize: 14, borderRadius: 4, display: 'BYCLICK' }, customData: { dayIndex, attrIndex, dayNumber: day.day }, label: { content: `第${day.day}天 ${attrIndex + 1}`, color: dayColor, fontSize: 12, bgColor: '#ffffff', padding: 4, borderRadius: 4, borderWidth: 1, borderColor: dayColor } }); dayPoints.push({ latitude: attraction.location.latitude, longitude: attraction.location.longitude }); }); // 添加当天路线 if (dayPoints.length > 1) { polylines.push({ points: dayPoints, color: dayColor, width: 4, arrowLine: true, dottedLine: false }); } // 添加到总集合 includePoints.push(...dayPoints); }); // 计算地图中心点和缩放级别 const { center, scale } = this.calculateMapViewport(includePoints); // 更新地图数据 this.setData({ 'mapData.markers': markers, 'mapData.polyline': polylines, 'mapData.latitude': center.latitude, 'mapData.longitude': center.longitude, 'mapData.scale': scale, 'mapData.showMap': true, 'mapData.includePoints': includePoints, 'mapData.dayColors': dayColors }); // 播放路线动画 this.playRouteAnimation(); }, // 计算地图视野 calculateMapViewport(points) { if (!points || points.length === 0) { return { center: { latitude: 39.9042, longitude: 116.4074 }, scale: 12 }; } // 计算所有点的边界 const lats = points.map(p => p.latitude); const lngs = points.map(p => p.longitude); const minLat = Math.min(...lats); const maxLat = Math.max(...lats); const minLng = Math.min(...lngs); const maxLng = Math.max(...lngs); // 计算中心点 const center = { latitude: (minLat + maxLat) / 2, longitude: (minLng + maxLng) / 2 }; // 计算缩放级别 const latRange = maxLat - minLat; const lngRange = maxLng - minLng; const maxRange = Math.max(latRange, lngRange); // 根据范围大小调整缩放级别 let scale = 15 - Math.floor(maxRange * 15); scale = Math.max(10, Math.min(scale, 17)); return { center, scale }; }, // 初始化地图控件 initMapControls() { this.mapCtx = wx.createMapContext('routeMap', this); // 确保这些方法已经定义 if (typeof this.handleRegionChange === 'function' && typeof this.handleMarkerTap === 'function') { this.setData({ handleRegionChange: this.handleRegionChange.bind(this), handleMarkerTap: this.handleMarkerTap.bind(this) }); } else { console.error('地图事件处理方法未定义'); // 提供默认的空函数 this.setData({ handleRegionChange: () => {}, handleMarkerTap: () => {} }); } }, handleRegionChange(e) { console.log('地图区域变化:', e); // 可以在这里添加地图区域变化时的处理逻辑 }, // 景点详情显示处理 handleMarkerTap(e) { const markerId = e.detail.markerId; const marker = this.data.mapData.markers.find(m => m.id === markerId); if (!marker || !this.data.planData || !this.data.planData.days) { console.error('无法找到标记或行程数据'); return; } const { dayIndex, attrIndex } = marker.customData; const attraction = this.data.planData.days[dayIndex].attractions[attrIndex]; if (!attraction) { console.error('无法找到景点数据'); return; } // 显示加载状态 this.setData({ showAttractionDetail: true, currentAttraction: { ...attraction, isLoadingImage: true // 添加加载状态 }, attractionDetail: { name: attraction.name, image: '/images/placeholder-image.jpg', // 先显示占位图 description: attraction.description, history: attraction.history_significance, address: attraction.address, open_hours: attraction.open_hours, ticket_info: attraction.ticket_info, visiting_etiquette: attraction.visiting_etiquette || [ "请保持庄严肃穆", "勿大声喧哗", "纪念馆内请勿拍照" ], location: attraction.location } }); // 自动获取景点图片 this.fetchAttractionImage(attraction) .then(imageUrl => { // 更新景点图片 this.setData({ 'currentAttraction.image': imageUrl, 'currentAttraction.isLoadingImage': false, 'attractionDetail.image': imageUrl }); // 同时更新planData中的对应景点图片 this.updateAttractionImage(attraction.id, attraction.name, imageUrl); }) .catch(err => { console.error('获取景点图片失败:', err); // 使用默认图片 this.setData({ 'currentAttraction.image': '/images/default-attraction.jpg', 'currentAttraction.isLoadingImage': false, 'attractionDetail.image': '/images/default-attraction.jpg' }); }); }, // 切换地图显示的天数 switchMapDay(e) { const dayIndex = e.currentTarget.dataset.day; this.setData({ currentMapDay: dayIndex }); // 重新处理地图数据,只显示选中天数的景点 this.processMapData(this.data.planData.days, dayIndex); }, // 地图缩放控制 handleZoomIn() { this.setData({ 'mapData.scale': Math.min(this.data.mapData.scale + 1, 18) }); }, handleZoomOut() { this.setData({ 'mapData.scale': Math.max(this.data.mapData.scale - 1, 10) }); }, // 重新定位到所有景点 resetToAllAttractions() { this.setData({ currentMapDay: -1 }); // 重新处理地图数据,显示所有景点 this.processMapData(this.data.planData.days); }, // 播放路线动画 playRouteAnimation() { if (this.data.mapData.polyline.length === 0) return; // 初始状态:所有路线半透明 const transparentPolylines = this.data.mapData.polyline.map(line => ({ ...line, color: line.color + '80' // 添加透明度 })); this.setData({ 'mapData.polyline': transparentPolylines }); // 逐个显示路线 let i = 0; const timer = setInterval(() => { if (i >= this.data.mapData.polyline.length) { clearInterval(timer); return; } const updatedPolylines = [...this.data.mapData.polyline]; updatedPolylines[i] = { ...updatedPolylines[i], color: this.data.mapData.polyline[i].color.replace('80', '') // 移除透明度 }; this.setData({ 'mapData.polyline': updatedPolylines }); i++; }, 500); }, // 导航到景点 navigateToAttraction() { if (!this.data.currentAttraction || !this.data.currentAttraction.location) { wx.showToast({ title: '无法获取景点位置', icon: 'none' }); return; } const { latitude, longitude } = this.data.currentAttraction.location; const name = this.data.currentAttraction.name || '红色教育基地'; wx.openLocation({ latitude, longitude, name, scale: 18 }); }, // 生成每日总结 generateDaySummary(day, attractions) { const redAttractions = attractions.filter(att => att.is_red_tourism); const attractionNames = redAttractions.map(att => att.name).join('、'); return `今日参观了${redAttractions.length}个红色教育基地${ attractionNames ? `,包括${attractionNames}` : '' },${day.summary || '通过实地参观加深了对革命历史的理解。'}`; }, // 创建每日行程安排 createDailySchedule(attractions, transport) { const timeSlots = ["09:00", "11:00", "14:00", "16:00"]; return attractions.map((att, index) => ({ time: timeSlots[index] || "10:00", duration: 120, attraction: att, transport: index < attractions.length - 1 ? transport : null, transport_duration: "约30分钟", transport_cost: this.getTransportCost(transport) })); }, // 获取交通费用估算 getTransportCost(transportType) { const costs = { 'public': '约5-10元/人', 'drive': '油费约20-50元', 'walk': '免费', 'bike': '免费' }; return costs[transportType] || '费用待定'; }, // 计算统计数据 calculateStatistics(days) { let redCount = 0; let totalHours = 0; days.forEach(day => { redCount += day.attractions.filter(a => a.is_red_tourism).length; totalHours += 8; // 每天按8小时计算 }); return { red_attractions_count: redCount, learning_points: redCount * 2, total_hours: totalHours, total_days: days.length }; }, // 获取红色景点图片 getRedTourismImage(isRed) { const images = { true: '/images/red-attraction.jpg', false: '/images/normal-attraction.jpg' }; return images[isRed] || '/images/default-attraction.jpg'; }, // 生成参观时间 generateVisitTime(index) { const times = ["09:00", "11:00", "14:00", "16:00"]; return times[index] || "10:00"; }, // 深度合并对象 deepMerge(target, source) { if (typeof source !== 'object' || source === null) { return target; } for (const key in source) { if (source.hasOwnProperty(key)) { if (typeof source[key] === 'object' && source[key] !== null && !Array.isArray(source[key])) { if (!target[key]) { target[key] = {}; } this.deepMerge(target[key], source[key]); } else { target[key] = source[key]; } } } return target; }, // 显示景点详情 async showAttractionDetail(e) { const attraction = e.currentTarget.dataset.attraction; // 设置当前景点信息,并标记图片正在加载 this.setData({ showAttractionDetail: true, currentAttraction: { ...attraction, isLoadingImage: true, image: '/images/placeholder-image.jpg' // 临时占位图 }, isFavorite: false // 先设置为false,等异步检查完成后再更新 }); // 自动获取景点图片 this.fetchAttractionImage(attraction).then(imageUrl => { this.setData({ 'currentAttraction.image': imageUrl, 'currentAttraction.isLoadingImage': false }); }).catch(err => { console.error('获取景点图片失败:', err); this.setData({ 'currentAttraction.isLoadingImage': false, 'currentAttraction.image': '/images/image-load-failed.jpg' // 加载失败的图片 }); }); }, // 计算两点间距离 calculateDistance(loc1, loc2) { if (!loc1 || !loc2) return 0; const R = 6371; // 地球半径(km) const dLat = (loc2.latitude - loc1.latitude) * Math.PI / 180; const dLon = (loc2.longitude - loc1.longitude) * Math.PI / 180; const a = Math.sin(dLat/2) * Math.sin(dLat/2) + Math.cos(loc1.latitude * Math.PI / 180) * Math.cos(loc2.latitude * Math.PI / 180) * Math.sin(dLon/2) * Math.sin(dLon/2); const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); return R * c; }, // 导航到景点 navigateToAttraction() { const { currentAttraction } = this.data; if (!currentAttraction || !currentAttraction.location) { wx.showToast({ title: '无法获取景点位置', icon: 'none' }); return; } wx.showLoading({ title: '准备导航中', mask: true }); // 检查位置权限 wx.authorize({ scope: 'scope.userLocation', success: () => { wx.openLocation({ latitude: currentAttraction.location.latitude, longitude: currentAttraction.location.longitude, name: currentAttraction.name, scale: 18, complete: () => { wx.hideLoading(); } }); }, fail: () => { wx.hideLoading(); wx.showModal({ title: '权限不足', content: '需要获取您的位置信息才能导航', confirmText: '去设置', success: (res) => { if (res.confirm) { wx.openSetting(); } } }); } }); }, // 关闭弹窗 closeAttractionDetail() { this.setData({ showAttractionDetail: false }); }, // 查找景点对象 findAttractionById(id) { if (!this.data.planData || !this.data.planData.days) return null; for (const day of this.data.planData.days) { for (const attr of day.attractions) { if (attr.id === id) return attr; } } return null; }, // 保存行程 savePlan() { if (!this.data.planData) { wx.showToast({ title: '没有可保存的行程', icon: 'none' }); return; } wx.showLoading({ title: '保存中...', mask: true }); this.setData({ isLoading: true }); const planData = this.data.planData; // 获取本地存储的token const token = wx.getStorageSync('token'); if (!token) { wx.showToast({ title: '请先登录', icon: 'none' }); wx.hideLoading(); this.setData({ isLoading: false }); return; } wx.request({ url: app.globalData.apiBaseUrl + '/api/red-tourism/save-plan/', method: 'POST', data: { plan_data: planData, city_ids: [Number(this.data.selectedCity)] // 添加城市ID }, header: { 'Content-Type': 'application/json', 'Authorization': 'Token ' + wx.getStorageSync('token') // 添加token到请求头 }, success: (res) => { if (res.statusCode === 201) { wx.showToast({ title: '保存成功', icon: 'success', duration: 2000 }); this.setData({ currentStep: 3 }); } else { this.handleApiError(res); } }, fail: (err) => { this.handleNetworkError(err); }, complete: () => { wx.hideLoading(); this.setData({ isLoading: false }); } }); }, // 处理API错误 handleApiError(res) { let errorMsg = '保存失败,请稍后再试'; if (res.data?.message) { errorMsg = res.data.message; } else if (res.statusCode === 401) { errorMsg = '请先登录'; // 可以在这里添加跳转到登录页面的逻辑 wx.navigateTo({ url: '/pages/login/login' }); } else if (res.statusCode === 400) { if (res.data?.errors) { errorMsg = Object.values(res.data.errors).join('\n'); } else { errorMsg = '数据格式不正确'; } } wx.showToast({ title: errorMsg, icon: 'none', duration: 3000 }); }, // 处理网络错误 handleNetworkError(err) { let errorMsg = '网络连接失败'; if (err.errMsg.includes('timeout')) { errorMsg = '请求超时,请检查网络连接'; } wx.showToast({ title: errorMsg, icon: 'none', duration: 2000 }); }, // 重新生成行程 regeneratePlan() { if (this.data.isRegenerating) return; this.setData({ isRegenerating: true }); wx.showLoading({ title: '正在重新规划...', mask: true }); // 修正请求数据结构 const requestData = { plan_id: this.data.planData?.id || null, city_ids: [Number(this.data.selectedCity)], days: Number(this.data.selectedDays), interests: [...this.data.selectedInterests, 'red-tourism'], transport: this.data.selectedTransport, custom_requirements: this.data.customRequirements, previous_plan: this.data.planData || { days: [] } // 确保有这个字段 }; console.log('重新规划请求数据:', JSON.stringify(requestData)); wx.request({ url: app.globalData.apiBaseUrl + '/api/red-tourism/regenerate/', method: 'POST', data: requestData, header: { 'Content-Type': 'application/json', 'Authorization': 'Token ' + wx.getStorageSync('token') }, success: (res) => { console.log('重新规划响应:', res); if (res.statusCode === 200) { if (res.data && res.data.status === 'success' && res.data.data) { this.processAndShowPlan(res.data.data, true); wx.showToast({ title: '重新生成成功', icon: 'success', duration: 2000 }); } else { this.handleError(res.data?.message || '返回数据格式不正确'); } } else { // 显示详细的错误信息 let errorMsg = `请求失败: ${res.statusCode}`; if (res.data && res.data.errors) { errorMsg += ` - ${JSON.stringify(res.data.errors)}`; } this.handleError(errorMsg); } }, fail: (err) => { console.error('重新规划请求失败:', err); this.handleError('网络请求失败,请检查连接'); }, complete: () => { this.setData({ isRegenerating: false }); wx.hideLoading(); } }); }, // 查看完整行程 viewPlan() { wx.navigateTo({ url: '/pages/plan-detail/plan-detail?plan=' + encodeURIComponent(JSON.stringify(this.data.planData)) + '&is_red_tourism=true' }); }, // 分享行程 sharePlan() { if (!this.data.planData) return; wx.showShareMenu({ withShareTicket: true }); }, onShareAppMessage() { if (!this.data.planData) return {}; return { title: this.data.planData.title || '我的红色之旅行程', path: '/pages/plan-detail/plan-detail?plan=' + encodeURIComponent(JSON.stringify(this.data.planData)) + '&is_red_tourism=true', imageUrl: this.data.planData.days[0]?.attractions[0]?.image || '/images/red-tourism-share.jpg' }; }, // 错误处理 handleError(message) { console.error('Error:', message); wx.showToast({ title: message, icon: 'none', duration: 2000 }); this.setData({ currentStep: 1, isLoading: false, planGenerated: false }); }, handleApiError(res) { let errorMsg = '请求失败,请稍后再试'; if (res.data?.message) { errorMsg = res.data.message; } else if (res.statusCode === 400) { errorMsg = '输入数据有误,请检查后重试'; } else if (res.statusCode === 500) { errorMsg = '服务器错误,请稍后再试'; } wx.showToast({ title: errorMsg, icon: 'none' }); this.setData({ isLoading: false }); }, handleNetworkError(err) { let errorMsg = '网络连接失败'; if (err.errMsg.includes('timeout')) { errorMsg = '请求超时,请检查网络连接'; } wx.showToast({ title: errorMsg, icon: 'none' }); this.setData({ isLoading: false }); }, // 返回上一步 navigateBack() { if (this.data.currentStep > 1) { this.setData({ currentStep: this.data.currentStep - 1 }); } else { wx.navigateBack(); } }, viewPlan() { wx.navigateTo({ url: '/pages/wodexingcheng/wodexingcheng' }); }, });