简介:这是一套面向计算机专业本科生的毕业设计级微信小程序项目,聚焦旅游社交场景,为开发者提供从需求分析到上线部署的完整实践范例。资源包含1429个文件,主体为217个JS逻辑文件、158个Vue组件、159个Java后端代码、124个JSON配置及116个WXSS样式文件,覆盖小程序前端、Spring Boot后端及配套静态资源,包体33.5MB,结构清晰、模块解耦度高。已有245人学习下载,适合希望掌握微信小程序全栈开发、社交功能实现(如动态发布、点赞评论、好友推荐)、旅行工具集成(LBS定位、攻略检索、日记记录)及工程化构建流程的学习者。预览可见install/run/build三阶段批处理脚本与大量带.bak后缀的备份组件,表明项目历经多轮迭代,附带完整开发环境配置说明与可直接运行的调试入口,便于快速复现与二次开发。
1. 这不是又一个“旅游笔记”小程序——它用 Vue 单文件组件 + 微信原生能力,把社交动线压进 3 层页面跳转内
你见过用户发完一条旅行动态,3 秒内就收到同城驴友私聊邀约的小程序吗?这个毕业设计级项目不靠后端推送堆性能,而是用uni-app构建跨端基底,但所有页面逻辑、路由跳转、数据绑定全部走微信小程序原生生命周期——onLoad里拉取附近景点,onShow中刷新未读消息,onPullDownRefresh触发日记列表重载。它没用云开发,却把wx.getLocation和wx.chooseImage的回调链拆成可复用的 mixin;它没接入第三方地图 SDK,但通过wx.openLocation+ 高德 POI 接口拼接,实现「点击景点卡片→唤起高德 App 导航」的无缝跳转。适合正在做毕设、需要快速交付可演示系统的学生,也适合想吃透pages.json路由配置与tabBar动态切换逻辑的初级开发者。如果你的痛点是:「页面多了路由乱套」「图片上传后路径不一致」「登录态在多个页面间丢失」,这个包里的.bak文件就是调试痕迹——它们不是废文件,而是你定位update-password.vue中wx.setStorageSync('token')执行时机的关键路标。
2. 从1-install.bat到main.css.bak:逆向解析构建流程与样式隔离机制
2.1 构建脚本链:.bat文件暴露的真实依赖关系
项目根目录下三个批处理文件不是摆设。1-install.bat实际执行的是:
npm install && npm install -D @dcloudio/uni-cli@2.0.1-32920231115001注意版本号2.0.1-32920231115001—— 这是 HBuilderX 3.7.2 内置的 uni-app CLI 特定快照版,不能直接升级到最新版 uni-app,否则IndexAsideStatic.vue.bak中的<uni-list>组件会因 API 变更报错。2-run.bat启动命令为:
npx uni-app-cli serve --mp-weixin --port 8080关键参数--mp-weixin指定仅编译微信小程序平台,避免多端构建干扰。而3-build.bat的核心是:
npx uni-app-cli build --mp-weixin --watch--watch参数让构建过程监听pages/下.vue文件变更,但不会自动重启 dev server——你需要手动刷新微信开发者工具中的模拟器。
提示:若
2-run.bat报错Cannot find module 'vue',说明node_modules中vue版本与@dcloudio/uni-app不匹配。此时应删除node_modules并执行1-install.bat,而非npm install vue@2.6.14单独安装。
2.2.bak文件:被遗忘的调试证据与样式污染溯源点
main.css.bak不是备份文件,而是main.css的上一版生产环境样式快照。对比二者差异可发现:
main.css中.travel-card { margin: 12rpx; }→main.css.bak中为margin: 16rpx;main.css删除了@import './common/reset.css';行
这说明开发者曾尝试全局重置样式,但发现uni-app的view组件默认margin为 0,导致卡片间距异常,最终回退并改用局部margin。同理,update-password.vue.bak比正式版多出一行:
<!-- <input v-model="formData.oldPassword" password /> -->注释掉的密码输入框证明:该页面最初设计支持「旧密码校验」,后因微信登录态已含 token 校验,被简化为单步修改。这种.bak文件是排查「为什么这个页面表单提交没反应」的第一线索——检查update-password.vue中bindsubmit事件是否绑定到form标签,而非button。
2.3pages.json路由配置:tabBar 与非 tabBar 页面的混合管理策略
pages.json定义了 7 个页面,其中 4 个属于tabBar:
{ "tabBar": { "color": "#7A7E83", "selectedColor": "#3cc51f", "borderStyle": "black", "list": [ { "pagePath": "pages/index/index", "text": "首页", "iconPath": "static/images/tabbar/home.png", "selectedIconPath": "static/images/tabbar/home-active.png" } ] }, "subNVue": [] }但pages/diary/detail是非 tabBar 页面,其跳转必须显式调用:
uni.navigateTo({ url: '/pages/diary/detail?id=' + item.id })关键陷阱:IndexHeader.vue.bak中曾存在uni.switchTab({ url: '/pages/diary/list' }),但/pages/diary/list并未在tabBar.list中声明,导致真机报错「navigateTo:fail page is not defined」。正确做法是:非 tabBar 页面用navigateTo,tabBar 页面才用switchTab。BreadCrumbs.vue.bak中的面包屑组件正是为解决此类跳转混乱而设计——它通过getCurrentPages()获取页面栈,动态生成返回路径。
3. 社交功能落地:从「发布旅行计划」到「实时点赞数更新」的全链路实现
3.1 发布流程:uni-file-picker与wx.uploadFile的协同控制
用户在pages/post/create.vue发布动态时,图片选择使用uni-file-picker组件:
<uni-file-picker file-mediatype="image" :limit="3" @select="onSelect" @delete="onDelete" :value="fileList" />但@select回调中不直接上传,而是先调用wx.chooseImage获取临时路径:
onSelect(e) { const tempFiles = e.tempFiles this.fileList = tempFiles.map(item => ({ ...item, url: item.path // 注意:此处 path 是本地临时路径,非网络 URL })) }上传动作发生在表单提交时:
submitForm() { this.fileList.forEach((file, index) => { const uploadTask = wx.uploadFile({ url: 'https://api.example.com/upload', // 后端接收地址 filePath: file.url, // 必须是本地路径 name: 'file', // 后端接收字段名 formData: { type: 'travel_image', index }, // 附带业务参数 success: (res) => { const data = JSON.parse(res.data) this.uploadedUrls.push(data.url) // 存储返回的 CDN 地址 } }) }) }注意:
wx.uploadFile的filePath必须是wx.chooseImage返回的tempFilePaths[0],不能是uni-file-picker的e.tempFiles[0].path(后者在某些机型上不可用)。这是真机调试时图片上传失败的最常见原因。
3.2 点赞状态同步:wx.setStorageSync与uni.$on的轻量级状态管理
点赞按钮位于pages/travel/detail.vue,其状态不依赖后端轮询,而是通过uni.$emit/uni.$on实现跨页面通信:
// detail.vue 中点赞触发 handleLike() { const newLikeCount = this.likeCount + (this.hasLiked ? -1 : 1) this.hasLiked = !this.hasLiked this.likeCount = newLikeCount // 本地持久化 const likedIds = wx.getStorageSync('liked_ids') || [] if (this.hasLiked) { likedIds.push(this.postId) } else { const idx = likedIds.indexOf(this.postId) if (idx > -1) likedIds.splice(idx, 1) } wx.setStorageSync('liked_ids', likedIds) // 广播给首页列表页 uni.$emit('update-like-count', { postId: this.postId, count: newLikeCount, liked: this.hasLiked }) }首页pages/index/index.vue监听该事件:
onLoad() { uni.$on('update-like-count', (data) => { const idx = this.postList.findIndex(item => item.id === data.postId) if (idx > -1) { this.postList[idx].likeCount = data.count this.postList[idx].hasLiked = data.liked } }) }这种模式规避了频繁请求后端接口,但需注意:uni.$on必须在onLoad中注册,且在onUnload中取消监听,否则内存泄漏:
onUnload() { uni.$off('update-like-count') }3.3 评论区渲染:scroll-view与v-for的性能临界点控制
评论列表使用scroll-view而非uni-list,因其支持scroll-into-view定位:
<scroll-view scroll-y :scroll-into-view="'comment-' + focusCommentId" style="height: 400rpx;" > <view v-for="(comment, index) in commentList" :key="comment.id" :id="'comment-' + comment.id" > <view class="comment-item"> <text class="user-name">{{ comment.userName }}</text> <text class="comment-text">{{ comment.content }}</text> </view> </view> </scroll-view>当评论数超过 50 条时,v-for渲染明显卡顿。解决方案是分页加载:
data() { return { commentList: [], currentPage: 1, pageSize: 10, hasMore: true } }, methods: { loadComments() { if (!this.hasMore) return uni.request({ url: `/api/comments?postId=${this.postId}&page=${this.currentPage}&size=${this.pageSize}`, success: (res) => { this.commentList = this.currentPage === 1 ? res.data.list : [...this.commentList, ...res.data.list] this.hasMore = res.data.hasNext this.currentPage++ } }) } }并在scroll-view底部添加触底监听:
<scroll-view @scrolltolower="loadComments" ... >4. 旅行工具模块:位置服务与高德 POI 数据的本地化对接
4.1wx.getLocation的精度分级与 fallback 策略
pages/index/index.vue的「附近景点」功能首先调用:
wx.getLocation({ type: 'gcj02', // 必须用国测局坐标系,高德要求 success: (res) => { this.latitude = res.latitude this.longitude = res.longitude this.fetchNearbyPOI() }, fail: (err) => { // 第一 fallback:尝试 wgs84 坐标系 wx.getLocation({ type: 'wgs84', success: (res2) => { // 转换 wgs84 → gcj02(使用开源转换库) const gcj = wgs84togcj02(res2.latitude, res2.longitude) this.latitude = gcj.lat this.longitude = gcj.lng this.fetchNearbyPOI() } }) } })fetchNearbyPOI()方法拼接高德 API:
fetchNearbyPOI() { const url = `https://restapi.amap.com/v3/place/nearby?key=YOUR_KEY&location=${this.longitude},${this.latitude}&types=风景名胜&radius=5000&offset=10` uni.request({ url, success: (res) => { this.nearbyPlaces = res.data.pois.map(poi => ({ id: poi.id, name: poi.name, address: poi.address, distance: Math.round(poi.distance) + 'm' })) } }) }注意:高德 key 需在
manifest.json中配置为amap字段,否则真机无法调用。manifest.json示例:{ "name": "travel-social", "appid": "", "description": "", "versionName": "1.0.0", "versionCode": "100", "transformPx": false, "app-plus": { "usingComponents": true, "nvueStyleCompiler": "uni-app", "splashscreen": { "alwaysShowBeforeRender": true, "waiting": true } }, "mp-weixin": { "appid": "wx1234567890", "usingComponents": true, "amap": "your-amap-key-here" } }
4.2 「跳转高德 App」的深度链接实现与降级方案
景点卡片点击事件:
openInGaode(poi) { // 优先尝试唤起高德 App const gaodeUrl = `iosamap://navi?sourceApplication=travel&lat=${poi.latitude}&lon=${poi.longitude}&dev=1&style=2` // 注意:微信内置浏览器不支持 iosamap:// 协议,需用 webview 加载高德网页版 if (uni.getSystemInfoSync().platform === 'ios') { uni.navigateTo({ url: `/pages/webview/gaode?url=${encodeURIComponent(gaodeUrl)}` }) } else { // Android 可直接唤起 uni.openLocation({ latitude: poi.latitude, longitude: poi.longitude, name: poi.name, address: poi.address }) } }pages/webview/gaode.vue内容:
<template> <web-view :src="url"></web-view> </template> <script> export default { data() { return { url: '' } }, onLoad(options) { this.url = decodeURIComponent(options.url) } } </script>5. 毕业设计交付关键:uni-app项目结构优化与微信开发者工具调试技巧
5.1pages/目录的层级压缩与subNVue的合理使用
原始项目中pages/diary/下有list.vue、detail.vue、edit.vue三个文件,但detail.vue中的「编辑」按钮跳转至edit.vue时,页面栈过深。优化方案是将edit.vue改为subNVue:
// pages.json { "path": "pages/diary/detail", "style": { "navigationBarTitleText": "旅行日记", "enablePullDownRefresh": true, "subNVues": [{ "path": "subNVue/diary-editor", "id": "diary-editor", "type": "popup", "style": { "width": "100%", "height": "80%", "bottom": "0", "left": "0", "zIndex": 999 } }] } }subNVue/diary-editor.vue使用uni.$emit通知父页面保存:
saveDiary() { uni.$emit('diary-saved', this.formData) uni.hideSubNVue('diary-editor') }父页面监听:
uni.$on('diary-saved', (data) => { // 更新本地数据 this.diaryContent = data.content })此方案减少页面栈压力,且subNVue支持原生动画,比navigateTo更流畅。
5.2 微信开发者工具真机调试:抓包与 storage 查看的实操路径
在微信开发者工具中,点击右上角「...」→「调试」→「Network」可查看所有请求。但注意:wx.uploadFile的请求不会出现在 Network 面板,需在 Console 中输入:
// 查看当前 storage 中的点赞记录 wx.getStorageInfoSync() // 查看具体键值 wx.getStorageSync('liked_ids')若需导出storage数据用于答辩演示,执行:
// 在 Console 中运行 const data = wx.getStorageInfoSync() console.log('Storage keys:', data.keys) data.keys.forEach(key => { console.log(key, ':', wx.getStorageSync(key)) })复制输出结果,粘贴至文本编辑器即可生成答辩用的「本地数据状态报告」。
5.3uni-app编译产物分析:dist/build/mp-weixin/中的关键文件映射表
| 源文件路径 | 编译后路径 | 作用说明 |
|---|---|---|
pages/index/index.vue | dist/build/mp-weixin/pages/index/index.js | 页面逻辑,含onLoad、onShow等生命周期 |
static/images/tabbar/home.png | dist/build/mp-weixin/static/images/tabbar/home.png | tabBar 图标,路径必须与pages.json严格一致 |
components/uni-list/uni-list.vue | dist/build/mp-weixin/components/uni-list/uni-list.js | 自定义组件,注意props类型校验在编译后仍生效 |
main.css | dist/build/mp-weixin/app.wxss | 全局样式,@import语句已被展开 |
验证编译是否成功,只需检查dist/build/mp-weixin/app.json是否包含所有pages配置项,且无window字段(微信小程序不支持window配置)。
提示:若
1-install.bat执行后node_modules/@dcloudio/uni-app目录下缺少lib子目录,说明安装不完整。此时应手动执行npm run build:mp-weixin,强制生成编译器依赖。
当dist/build/mp-weixin目录生成完毕,直接将其拖入微信开发者工具的「导入项目」窗口,选择「小程序」类型,AppID 填写wx1234567890(测试用),即可启动调试。首次运行时,IndexHeader.vue.bak中注释掉的console.log('header loaded')会出现在 Console 中——这是确认页面加载顺序的最简验证方式。
本文还有配套的精品资源,点击获取