12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550 |
- // 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' });
- },
- });
|