Vue多页面WEBGIS旅游查询系统:从源码运行到地图开发实战
2026/9/16 14:42:36 网站建设 项目流程

简介:这是一套基于Vue与WEBGIS技术构建的旅游综合查询系统前端源码,主要面向计算机相关专业在校生、毕业设计及课程设计使用者,也可作为初学Vue与地图应用开发者的参考项目。压缩包共102个文件,约3.66MB,其中以50个Vue单文件组件作为主体,辅以16个JS逻辑脚本、8个HTML页面、8个JSON配置数据,以及CSS、PNG/JPG图标、文档等类型,覆盖页面展示、地图交互、数据组织与样式适配等多个前端开发环节。从内容结构看,系统包含台风路径、地图导航、POI兴趣点等典型旅游查询模块,能够完整演示基于地图的查询与信息展示流程。目前已有281人学习下载,项目结构清晰、功能验证通过,既适合用于课程设计或毕业设计演示,也便于在此基础上扩展其他地图业务场景。解压后建议使用英文路径运行,项目整体对Vue组件化开发与WEBGIS应用实践有较好的借鉴价值。

1. 拿到Vue WEBGIS源码包,先别急着npm run serve

这个基于Vue的旅游综合查询系统源码,并不是一个只包含单一入口的传统Vue单页应用。解压后你会看到index.html、typhoon.html、mapdaohang.html、POI.html和global.css这几个关键文件,它们分别对应旅游查询系统的首页、台风路径展示、地图导航和兴趣点检索四个核心模块。很多人拿到源码后直接执行npm run serve,随后遇到地图白屏、接口跨域或者页面跳转404,问题往往出在对这种多页面结构的理解上。这篇博文从文件组织、地图初始化、业务数据对接和样式定制四个层面,把项目从运行到二次开发的关键节点拆开讲清楚。适合正在做毕业设计、课程设计的前端学生,也适合想快速改造一份WEBGIS源码的工程师。

2. 从global.css到POI.html:拆解Vue多页面文件组织与运行前置条件

2.1 为什么是多个html而不是单页应用?Vue多页面的目录约定

最常见的情况是,项目在脚手架阶段就选择了多页面应用(MPA)模式,或者由多个独立页面聚合在一起。相比SPA,这种结构更贴近传统WEBGIS项目:地图页面通常体积较大,拆分为多个入口可以按需加载,避免首屏一次性把地图库、路由和业务模块全部下载。Vue CLI的多页面配置通过vue.config.js中的pages字段声明每个入口,每个html文件就是对应页面的模板,内部通过<script><div id=...>与Vue实例关联。

项目里的文件对应关系可以参考这张表:

文件对应模块主要功能
index.html首页总体布局、导航入口、统计概览
mapdaohang.html地图导航地图展示、路径规划、图层控制
POI.html兴趣点检索搜索景点、酒店、餐饮并在地图上标记
typhoon.html台风路径展示台风轨迹及影响范围
global.css全局样式统一颜色、字体、卡片与地图容器样式

如果项目里配置了vue-router,那么这些页面之间的跳转通常使用window.location<a>标签,而不是路由组件。原因是每个html页面都是独立的Vue实例,路由跳转会重新加载整个页面,但好处是各功能模块之间互不干扰。在后期二次开发时,如果你想新增一个“攻略详情页”,只需要复制一个html模板,新增一个入口文件,再在pages里注册即可。

2.2 环境准备:Node、Vue CLI 与依赖安装命令

在启动项目前,先确认开发机已经有Node.js环境。常见做法是用node -vnpm -v检查版本,Node版本过低会导致Vue CLI运行异常。然后全局安装@vue/cli,如果项目里已经带了package-lock.json,直接执行npm install就能安装依赖。

node -v npm -v npm install -g @vue/cli npm install

这些命令的作用分别是:查看Node当前版本;查看npm版本;安装Vue命令行工具,提供vue servevue build等命令;在项目目录下安装依赖包。如果你的网络环境比较慢,可以换成npm install --registry=https://registry.npmjs.org,或者使用cnpm,但注意cnpm安装的依赖有时会出现路径结构不一致的问题。

装完依赖后,建议先看一眼package.json里的scripts字段,确认启动命令是否被修改过:

{ "scripts": { "serve": "vue-cli-service serve --host 0.0.0.0 --port 8080", "build": "vue-cli-service build", "lint": "vue-cli-service lint" } }

serve后面的--host 0.0.0.0表示允许局域网访问,手机调试时比较方便;--port指定端口,如果8080被占用,可以改成--port 8090build命令会把整个项目打包到dist目录,部署时直接把dist放到Nginx或Tomcat下即可。默认情况下,Vue CLI会把多页面入口都构建到dist根目录,所以最终生成的index.htmlPOI.html等都是平级的。

2.3 跑起来之前必须改掉的三个配置

2.3.1 项目路径不能包含中文和空格

项目说明里经常强调“项目名字和项目路径不要用中文”,这不是空话。WEBGIS项目中的地图库和构建工具在处理包含中文的资源路径时,可能在开发服务器启动时正常,但打包后静态资源加载会返回404。建议解压后直接重命名为英文名称,例如tourism-webgis,再放入D:\workspace这样的纯英文路径下。

2.3.2 为后端接口添加开发代理

旅游查询系统免不了要请求景点、POI或台风数据接口。在开发环境,最常见的做法是在vue.config.js里配置devServer.proxy,把/api开头的请求转发到后端服务,避免浏览器跨域限制。

// vue.config.js module.exports = { pages: { index: { entry: 'src/main.js', template: 'public/index.html', filename: 'index.html' }, poi: { entry: 'src/poi.js', template: 'public/POI.html', filename: 'POI.html' } }, devServer: { proxy: { '/api': { target: 'http://localhost:8081', changeOrigin: true, pathRewrite: { '^/api': '' } } } } }

这里pages下的每一项声明一个入口页面,entry指定JavaScript入口,template指定页面模板,filename是最终生成的页面名。proxy中的/api表示所有以/api开头的请求都会被代理到http://localhost:8081changeOrigin会修改请求头中的Host,pathRewrite/api前缀去掉,方便后端按原有接口路径处理。实际开发时,如果后端返回的字段是longitude而不是lng,你还需要在业务代码里做映射,这会在第四章详细说明。

2.3.3 地图容器需要显式设置高度

很多地图组件在初始化时计算容器的宽高,如果CSS没有给容器一个确定的高度,地图就会变成一块灰色区域或者只有一行图标。检查global.css里是否对#map这类选择器设置了明显的高度,没有的话补上:

#map, .map-container { width: 100%; height: 600px; }

这个高度可以根据页面布局调整,但必须是一个具体数值或百分比,不能只写auto。同时建议给地图容器加一个position: relative,后续添加覆盖物、水印或悬浮面板时会更容易定位。

3. 地图中心与底图加载:WEBGIS 模块的最小编程单元

3.1 Leaflet还是OpenLayers:如何在项目里识别地图引擎

在开始改地图之前,先确认项目用的哪个地图库。最直接的方法是在源码目录里全局搜索L.map(ol.Map(,前者是Leaflet创建地图的写法,后者是OpenLayers创建地图的写法。在命令行里可以从项目根目录执行:

grep -r "L.map(" src/ grep -r "ol.Map(" src/

如果没有装grep,也可以用IDE自带的全局搜索。知道地图引擎后,接下来的一切配置才会有针对性。两个库的坐标体系都基于WGS84,但投影和绘制API差异很大,没法直接套用。以Leaflet为例,创建一个地图实例通常需要指定中心点、缩放级别和底图图层。

3.2 把地图定位到目标景区:中心点与缩放级别参数

在天气、POI和旅游场景中,地图中心点通常设置为某个景区的经纬度。下面这段代码是项目里比较典型的Leaflet初始化写法:

// src/components/MapView.vue 或对应页面脚本 const map = L.map('map', { center: [30.659, 104.063], zoom: 12, zoomControl: true, attributionControl: true }); const baseLayer = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19, attribution: '&copy; OpenStreetMap contributors' }); baseLayer.addTo(map);

代码里center接收的是一个[纬度, 经度]数组,注意顺序是先纬后经,这和很多经纬度接口返回的“经度,纬度”格式正好相反,是实际开发中最常见的坑。zoom决定了地图缩放到第几级,数字越大越详细。maxZoom限制底图最大缩放层级,超出后会出现模糊。attribution是版权标注,一般保留原底图的说明,涉及商用数据源的场景尤其不要删除。

如果你的项目里使用的是OpenLayers,初始化代码通常长这样:

import Map from 'ol/Map'; import View from 'ol/View'; import TileLayer from 'ol/layer/Tile'; import OSM from 'ol/source/OSM'; const map = new Map({ target: 'map', layers: [new TileLayer({ source: new OSM() })], view: new View({ center: [116.4, 39.9], zoom: 10, projection: 'EPSG:3857' }) });

这段代码里center用的是投影坐标,而不是经纬度,需要调用ol/proj.fromLonLat转换。所以拿到源码后,先花十分钟确认库的类型,比盲目改坐标要节省更多时间。

3.3 底图切换与常见瓦片参数表

旅游系统通常需要多个底图供用户切换,常见做法是在地图右上角加一个图层控制按钮,底层切换的是不同瓦片源的地址。不同瓦片源对应不同的域名规则和参数,下表整理了常用底图的关键参数:

底图URL模板需要关注参数
OpenStreetMaphttps://{s}.tile.openstreetmap.org/{z}/{x}/{y}.pngsubdomains:abc,z/y/x
高德地图https://webrd0{s}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}需要申请Key,s为子域
天地图http://t{s}.tianditu.gov.cn/DataServer?T=vec_w&X={x}&Y={y}&L={z}&tk=你的密钥T参数区分矢量/影像/注记
CartoDBhttps://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png有light/dark风格可选

以天地图为例,T=vec_w是矢量底图,img_w是影像底图,cia_w是影像注记,很多学校项目会申请一个天地图key做全国范围标注,比纯英文底图中文化程度更高。如果项目里同时包含多个底图,可以在代码里维护一个图层对象数组,通过L.control.layers加入控件。需要注意的是,某些瓦片源对请求频率有限制,开发时频繁切换底图偶尔会出现403,间隔几秒再刷通常就恢复。

3.4 点击地图获取经纬度:二次开发最常用的调试入口

在旅游系统里,地图点击可以用来做逆向检索或者位置标记。在Leaflet中监听click事件,会返回一个latlng对象,其中latlng就是当前点击点的经纬度。这个功能在开发调试时非常有用,你可以快速把自己感兴趣的景区坐标打印出来,然后填进center配置里。

map.on('click', function (e) { console.log('点击坐标:', e.latlng.lat.toFixed(6), e.latlng.lng.toFixed(6)); var popup = L.popup() .setLatLng(e.latlng) .setContent('纬度: ' + e.latlng.lat.toFixed(6) + '<br>经度: ' + e.latlng.lng.toFixed(6)) .openOn(map); });

toFixed(6)保留六位小数,约等于0.1米的精度,完全够用于旅游点位的采集。每次点击都会在地图上弹出一个带坐标的气泡,这个调试信息不需要删除,保留到开发后期反而方便你验证点位是否正确。

4. POI检索与typhoon.html:业务数据的对接与边界处理

4.1 POI.html里的搜索逻辑:从输入框到地图标记

POI页面是旅游查询系统的核心,用户输入“宽窄巷子”后,前端要把关键词发给后端,拿到坐标和名称后在地图上画标记。项目里常见的请求代码是这样的:

// src/poi.js async function searchPOI(keyword) { const res = await fetch('/api/poi/search?keyword=' + encodeURIComponent(keyword), { headers: { 'Content-Type': 'application/json' } }); const data = await res.json(); console.log(data); data.list.forEach(item => { const marker = L.marker([item.lat, item.lng]).addTo(map); marker.bindPopup(` <div> <h3>${item.name}</h3> <p>${item.address}</p> <p>评分:${item.score}</p> </div> `); }); }

这段代码里,encodeURIComponent对中文关键词做编码,避免URL中出现非法字符。fetch默认使用GET请求,返回的data.list约定为景点数组。L.marker接收坐标数组,然后通过bindPopup绑定点击弹窗。接口返回字段通常包括latlngnameaddressscore,如果后端返回的是longitude/latitude这种命名,前端需要做一个字段映射:

const list = data.list.map(item => ({ lat: item.latitude, lng: item.longitude, name: item.name || '未命名', address: item.address || '', score: item.score || 0 }));

这种映射看起来简单,但能避免后期每处引用都要改字段名。nameaddress加空字符串兜底,是为了防止弹窗渲染时出现undefined

4.2 台风路径模块:为什么旅游系统里要有气象数据

typhoon.html这个页面乍看和旅游不相关,但它的作用其实是辅助出行决策:告知用户台风路径和影响范围,避免在恶劣天气期间安排户外景点。这个模块的实现通常包括两部分,一是请求台风的历史路径点,二是用折线或带箭头的线绘制轨迹。下面是一段用Leaflet绘制路径的最小示例:

const pathPoints = [ [18.1, 124.3], [19.5, 123.8], [20.9, 122.9], [22.7, 121.5] ]; const typhoonLine = L.polyline(pathPoints, { color: '#d93026', weight: 3, dashArray: null, lineCap: 'round' }); typhoonLine.addTo(map); const endPoint = pathPoints[pathPoints.length - 1]; L.circleMarker(endPoint, { radius: 8, color: '#d93026', fillColor: '#ff4d4f', fillOpacity: 0.6 }).addTo(map).bindPopup('当前台风中心');

参数上,pathPoints的每一项仍然是[纬度, 经度]weight控制线宽,dashArray用于虚线效果,如果希望显示运动趋势,可以配合L.arrow或旋转标记。circleMarker是固定像素大小的圆点,适合表示台风中心这类点要素,不会因为地图缩放而改变视觉大小。

如果后端返回的是实时位置数组,建议在绘制前按时间戳排序,否则折线会来回交叉。排序可以这样处理:

const sortedPoints = rawPoints .sort((a, b) => new Date(a.time) - new Date(b.time)) .map(item => [item.lat, item.lng]);

4.3 数据边界:接口返回为空和坐标异常时怎么处理

实际调用中,POI接口经常出现返回空列表、坐标为空或者名称重复的情况。如果直接遍历空数组,页面没有任何反馈;如果坐标缺失,标记会变成undefined导致地图渲染报错。一个相对稳妥的处理方式是先过滤数据:

const validList = (data.list || []).filter(item => { const lat = Number(item.lat); const lng = Number(item.lng); return isFinite(lat) && isFinite(lng) && lat !== 0 && lng !== 0; }); if (validList.length === 0) { alert('当前区域没有找到相关POI'); return; }

Number(item.lat)会把字符串转成数字,isFinite用来排除NaN和无穷值,同时把坐标为0这种无效占位值也过滤掉。这样至少能保证地图不崩,同时给用户一个明确提示。如果你做的是课程设计,这种防御性代码也算一个加分点。

4.4 联调时如何快速判断是前端问题还是后端问题

在对接真实接口时,我一般会在浏览器开发者工具的Network面板里先看请求是否发出,再看返回状态码和响应体。如果请求发出去了,但响应是401或跨域报错,那问题大概率在后端或代理配置。如果请求根本没发出,那就要检查事件绑定和按钮是否在这之前抛了异常。

一个更高效的做法是在fetch之前打印请求地址:

console.log('/api/poi/search?keyword=', keyword);

然后把地址直接粘贴到浏览器地址栏访问。如果浏览器直接能返回JSON,说明后端正常,问题在代理或前端参数。如果返回404或500,直接把这条URL甩给后端同学,联调效率会提升很多。

5. 把global.css改成自己的品牌样式:主题切换与地图适配技巧

5.1 利用CSS变量替换全局主色

很多课程设计的页面颜色是写死的,比如主题色在十几个文件中都出现#1890ff,改起来容易漏。项目里global.css作为全局样式,比较合适的方式是定义一组CSS变量:

:root { --brand-color: #1890ff; --bg-color: #f0f2f5; --card-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); } .primary-btn { background-color: var(--brand-color); border-color: var(--brand-color); } .nav-bar { background-color: var(--bg-color); box-shadow: var(--card-shadow); }

之后要换主题色,只需要改动:root里的变量值,全局引用了var(--brand-color)的地方会同步更新。但注意,如果某个模块的样式是通过JavaScript直接设置style.backgroundColor,CSS变量就管不到,需要在脚本里额外处理。

5.2 地图容器在移动端的三个适配参数

旅游查询系统大概率会在手机上打开,地图容器除了高度设置外,还应关注三个参数。第一个是viewport,必须在index.html<head>中设置<meta name="viewport" content="width=device-width, initial-scale=1.0">,否则移动端页面的宽度会自适应失败。第二个是地图交互手势,Leaflet默认支持触摸拖拽,但缩放按钮在手机上容易被手指遮挡,可以设置zoomControl: false,然后结合手势缩放。第三个是弹窗宽度,默认的Popup在手机上会超出屏幕,可以覆盖CSS:

.leaflet-popup-content-wrapper { max-width: 80vw; } .leaflet-popup-content { margin: 10px; }

80vw表示视口宽度的80%,这样弹窗内容再长也不会破坏页面布局。这三个参数改完后,最好用手机浏览器或开发者工具的移动端模拟器实际滑一遍地图,确认拖拽和缩放都没有遮挡。

5.3 验证修改是否生效的两种有效方式

修改样式后,直接在浏览器里硬刷新有时候会继续使用旧缓存。打开开发者工具后,在Network面板勾选Disable cache,并保持开发者工具打开的状态下刷新页面,能确保加载到最新的CSS。另一个方式是在地图初始化完成后的回调里输出一个控制台标记:

map.whenReady(() => { console.log('WEBGIS map ready', map.getCenter(), map.getZoom()); });

这样每次页面加载后,控制台会输出当前中心点和缩放级别,一旦确认地图加载成功,后续排查样式问题就能先排除地图自身异常。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询