CSDN文章
基于 Vue 3 + Vite + Leaflet + Pinia 开发的郑州文旅地图实战项目。覆盖 60+ 景点,实现地图展示、打卡签到、游戏化收集(物品掉落/作物种植/宝藏探索)、14个成就徽章、3 种地图主题 × 5 种季节主题、电子护照、AI 路线规划等完整功能闭环。纯前端 + localStorage 持久化,零服务器成本,并封装为 Electron桌面应用。适合学习 Vue 3 组合式
AI技能大赛——郑州文旅地图
title: Vue 3 + Vite + Leaflet 实战:开发一个完整的文旅地图应用(含 Electron 桌面封装)
date: 2026-05-15
tags: [Vue3, Vite, Pinia, Leaflet, TypeScript, Electron, 前端实战, 地图应用]
categories: 前端开发
一、项目简介
郑州文旅地图是一个基于 Vue 3 全家桶开发的交互式文旅地图应用。它覆盖了郑州 60+ 个景点(从 5A 级少林寺到现代地标二七塔),提供了打卡签到、游戏化收集、路线规划、多主题切换等一系列完整的用户体验。
核心亮点: 纯前端 + localStorage 持久化,无需后端服务器,构建后即可通过浏览器或 Electron 桌面应用使用。

功能清单
| 功能 | 说明 |
|---|---|
| 🗺️ 交互式地图 | Leaflet + 高德地图瓦片,支持缩放和平移 |
| 📍 景点标记 | 60+ 景点按 6 个类别分类展示 |
| ✅ 打卡签到 | 签到获得随机物品和成就徽章 |
| 🎮 游戏化系统 | 物品收集、作物种植、隐藏宝藏探索 |
| 🏆 成就徽章 | 14 个徽章,涵盖各种挑战目标 |
| 🎨 多主题切换 | 3 种地图风格 × 5 种季节主题 |
| 📖 电子护照 | 查看打卡记录、已获徽章 |
| 🤖 AI 路线规划 | 基于天气和评分的智能推荐 |
| 🖥️ 桌面应用 | Electron 封装,双击即可运行 |
二、核心技术栈
| 技术 | 版本 | 用途 |
|---|---|---|
| Vue 3 | ^3.5.34 | 组合式 API + <script setup> |
| Vite | ^8.0.12 | 构建工具 |
| Pinia | ^3.0.4 | 状态管理 |
| TypeScript | ~6.0.2 | 类型安全 |
| Leaflet | ^1.9.4 | 地图渲染引擎 |
| Electron | ^42.0.1 | 桌面应用封装 |
| Vue Router | ^4.6.4 | SPA 路由(Hash 模式) |
三、项目架构
src/
├── main.ts # 应用入口
├── App.vue # 根组件
├── router/index.ts # 路由(Hash 模式)
├── stores/ # 8 个 Pinia Store
│ ├── mapStore.ts # 地图状态
│ ├── poiStore.ts # POI 数据与搜索
│ ├── themeStore.ts # 主题管理
│ ├── checkinStore.ts # 打卡系统
│ ├── favoritesStore.ts # 收藏管理
│ ├── farmStore.ts # 农场系统
│ ├── routeStore.ts # 路线规划
│ └── uiStore.ts # UI 状态
├── components/
│ ├── map/MapViewport.vue # 核心地图组件
│ ├── panels/ # 侧边面板(POI详情/规划器/天气等)
│ ├── passport/ # 电子护照
│ ├── farm/ # 农场系统
│ └── ui/ # UI 组件(季节横幅/Toast等)
├── composables/ # 可组合函数
├── data/ # 数据层
│ ├── types/ # TypeScript 类型定义
│ ├── cities/ # 城市模块(目前只有郑州)
│ └── constants/ # 常量(地图样式/徽章/物品等)
├── logic/ # 业务逻辑
└── assets/styles/ # CSS 样式(含主题变量)
四、核心功能实现
4.1 地图引擎集成
地图使用 Leaflet 结合高德地图瓦片服务。初始化时锁定到郑州区域,限制缩放级别在地标可见范围内:
// src/components/map/MapViewport.vue (简化)
function initMap() {
map = L.map('map', {
center: [34.75, 113.68], // 郑州中心坐标
zoom: 11,
minZoom: 10,
maxBounds: [
[34.30, 112.80], // 西南角
[34.95, 114.20] // 东北角
]
})
initTileLayer(themeStore.mapStyle)
}
function initTileLayer(style: MapStyleId) {
const config = MAP_TILE_CONFIG[style]
return L.tileLayer(config.url, {
subdomains: config.subdomains.split(''),
attribution: config.attribution
})
}
通过 CSS filter 实现主题风格的动态切换。以赛博朋克主题为例:
/* 赛博朋克:色调旋转 180° + 高饱和 + 低亮度 */
.map-viewport[data-style="cyberpunk"] .leaflet-tile-pane {
filter: hue-rotate(180deg) saturate(2.5) brightness(0.65) contrast(1.2);
}
赛博朋克主题效果:
4.2 打卡签到 + 游戏化系统
打卡系统是应用的核心交互之一。每次打卡都会触发一套完整的奖励链:
// src/components/panels/PoiDetailCard.vue
function doCheckin() {
if (!selectedPoi.value) return
// 1. 检查现有徽章
const prevBadges = checkBadges(checkinStore.badgeCheckData)
// 2. 执行打卡
checkinStore.checkin(selectedPoi.value.id)
// 3. 检查是否解锁新徽章
const newBadges = checkBadges(checkinStore.badgeCheckData)
for (const badgeId of newBadges) {
if (!prevBadges.has(badgeId)) {
const badge = BADGES.find(b => b.id === badgeId)
showToast('success', '🏅 解锁新成就!', `${badge.name} — ${badge.desc}`)
}
}
// 4. 播放音效
audio.playCheckinSound()
// 5. 随机掉落物品
const item = rollRandomItem()
farmStore.addItem(item)
// 6. 探索隐藏宝藏(8% 概率)
if (Math.random() < 0.08) {
farmStore.markTreasureFound(poi.id)
showToast('info', '发现宝藏!', `隐藏宝藏已找到!`)
}
}
打卡效果:
4.3 14 个成就徽章
徽章系统覆盖了从新手到资深玩家的全阶段成就:
| 徽章 | 条件 | 难度 |
|---|---|---|
| 🚀 初出茅庐 | 首次签到 | ⭐ |
| 🏕️ 开路先锋 | 签到 10 次 | ⭐⭐ |
| 🏙️ 城市猎人 | 签到 30 次 | ⭐⭐⭐ |
| 🏆 制霸全城 | 签到全部 63 个 POI | ⭐⭐⭐⭐⭐ |
| 🌟 精英五档 | 签到全部 5A 景区 | ⭐⭐ |
| 🦉 夜猫子 | 晚上 8 点后签到 3 次 | ⭐⭐ |
| 🔥 持之以恒 | 连续签到 7 天 | ⭐⭐⭐⭐ |
| 🎭 三连击 | 同一天签到 3 个 POI | ⭐⭐⭐ |
徽章检查函数使用纯函数式设计,便于测试:
export function checkBadges(data: BadgeCheckData): Set<string> {
const unlocked = new Set<string>()
const { checkedCount, uniqueDays, categoryCounts, favCount, ... } = data
if (checkedCount >= 1) unlocked.add('first-step')
if (checkedCount >= 10) unlocked.add('trailblazer')
if (checkedCount >= 30) unlocked.add('city-hunter')
if (checkedCount >= 63) unlocked.add('full-house')
if (uniqueDays >= 7) unlocked.add('streak-7')
// ...
return unlocked
}
4.4 多主题切换系统
地图主题和季节主题是独立的两个维度,可以自由组合(3 × 5 = 15 种组合)。
地图主题通过切换高德瓦片的 style 参数 + CSS 滤镜实现:
// src/data/constants/mapStyles.ts
export const MAP_TILE_CONFIG = {
pixel: { url: '...&style=7', ... }, // 像素风
doodle: { url: '...&style=8', ... }, // 涂鸦风
cyberpunk: { url: '...&style=7', ... }, // 赛博朋克(靠 CSS 滤镜实现)
}
季节主题通过 CSS 变量覆盖实现,5 个季节各有一套独立的配色方案:
/* 春季 - 粉绿清新 */
[data-season="spring"] {
--sv-green: #6BAF8D;
--sv-gold: #D4A373;
--bg-card: #FAFAF5;
}
/* 秋季 - 金黄暖调 */
[data-season="autumn"] {
--sv-green: #8B7D3C;
--sv-gold: #C97D3C;
--bg-card: #FDF6EC;
}
/* 冬季 - 霜白冷色 */
[data-season="winter"] {
--sv-green: #5B8C7A;
--sv-gold: #B8A88A;
--bg-card: #F5F5F8;
}
4.5 农场种植系统
农场系统包含三个标签页:背包、种植、宝藏。
背包存放打卡获得的物品,按稀有度分为普通(绿色)、稀有(蓝色)、史诗(紫色):
// 物品掉落概率
const COMMON_RATE = 0.60 // 普通
const RARE_RATE = 0.30 // 稀有
const EPIC_RATE = 0.10 // 史诗
种植系统有 6 种作物,每种有各自的生长周期:
| 作物 | 生长时间 | 种子来源 |
|---|---|---|
| 🌾 小麦 | 1 天 | 打卡掉落 |
| 🥕 胡萝卜 | 1 天 | 打卡掉落 |
| 🌻 向日葵 | 2 天 | 打卡掉落 |
| 🍇 葡萄 | 3 天 | 打卡掉落 |
| 🌳 古树 | 5 天 | 稀有掉落 |
3 块田地支持同时种植,收获时根据作物类型获得奖励。
农场界面:
4.6 Electron 桌面封装
为了让用户像使用普通软件一样双击运行,使用 Electron 进行了桌面封装。
主进程配置:
// electron/main.cjs
const { app, BrowserWindow } = require('electron')
const path = require('path')
function createWindow() {
const win = new BrowserWindow({
width: 1200,
height: 800,
minWidth: 375,
minHeight: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.cjs'),
contextIsolation: true, // 安全:隔离渲染进程
nodeIntegration: false // 安全:禁用 Node 集成
}
})
win.loadFile(path.join(__dirname, '../dist/index.html'))
}
关键坑点: Vite 构建出的 index.html 默认使用绝对路径引用资源(如 /assets/index.js),但 Electron 通过 file:// 协议加载文件时,绝对路径会指向磁盘根目录。解决方案是在 vite.config.ts 中设置 base: './':
// vite.config.ts
export default defineConfig({
base: './', // 使用相对路径,兼容 Electron
plugins: [vue()],
resolve: {
alias: { '@': resolve(__dirname, 'src') }
}
})
桌面版效果:
五、本地运行教程
环境要求
- Node.js >= 18
- npm >= 9
步骤
# 1. 克隆项目
git clone https://github.com/yangyi785/zhengzhou-travel-map.git
cd zhengzhou-travel-map
# 2. 安装依赖
npm install
# 3. 启动开发服务器(浏览器)
npm run dev
# 4. 构建生产版本
npm run build
# 5. 启动 Electron 桌面版
npm run electron
或者直接双击项目根目录的 启动桌面版.bat(Windows),一键构建 + 启动桌面应用。
六、部署方式
本项目是纯静态 SPA,所有数据存储在代码和 localStorage 中,无需后端服务器,因此部署方式非常灵活:
- 静态托管: 构建后的
dist/目录可直接部署到 Vercel、Netlify、GitHub Pages 等 - 桌面应用: 构建后通过 Electron 直接在本地运行
- 本地预览:
npm run preview预览生产构建
七、项目亮点总结
- 零服务器成本 — 纯前端 + localStorage 持久化,无需搭建后端
- 完整离线可用 — 所有数据在本地,无网络也能正常使用
- 双维度主题系统 — 3 种地图风格 × 5 种季节主题 = 15 种视觉组合
- 游戏化闭环设计 — 打卡 → 掉物品 → 种作物 → 找宝藏 → 解锁成就,形成持续激励
- TypeScript 全覆盖 — 类型安全,代码可维护性强
- 模块化城市架构 — 城市数据独立模块化,易于扩展到其他城市
- 双端适配 — 浏览器端 + Electron 桌面端,一套代码全覆盖
八、源码地址
项目完整源码已开源至 GitHub:
https://github.com/yangyi785/zhengzhou-travel-map
欢迎 Star、Fork、下载学习或二次开发!
九、总结
本项目完整覆盖了从地图展示、数据管理、用户交互到桌面封装的全流程开发。通过这个项目,你可以学到:
- Vue 3 Composition API +
<script setup>的最佳实践 - Pinia 状态管理的模块化设计
- Leaflet 地图的集成与定制
- CSS 变量驱动的主题系统设计
- 纯前端游戏化系统设计
- Electron 桌面应用的封装技巧
后续可在此基础上拓展更多功能:多城市支持、用户登录同步、更多游戏化玩法等。
如果文章对你有帮助,欢迎点赞、收藏、关注,持续更新更多前端实战教程~
标签: Vue3 Vite Pinia Leaflet TypeScript Electron 前端实战 地图应用
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐
所有评论(0)