十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

用UniApp从0到1开发南昌旅行指南微信小程序

用UniApp从0到1开发南昌旅行指南微信小程序 1. 项目定位与整体设计思路1.1 为什么选 UniApp 来做微信小程序做南昌旅行指南这个项目我没有直接上手微信原生小程序而是选择了 UniApp 框架这里面的考虑值得先聊清楚。如果你只是打算做一个简单 demo原生小程序其实够用但一旦涉及多端复用、快速迭代、以及后续可能上架 App 的需求UniApp 的优势就体现出来了。它基于 Vue.js 语法对前端开发者非常友好尤其是已经会 Vue 的人几乎可以零成本切换过来。UniApp 的本质是一套代码编译到多端包括微信小程序、支付宝小程序、H5、iOS 和 Android App。对旅行指南这种内容型应用来说数据展示、详情页、地图导航这些功能在多个端都需要用 UniApp 写一遍就可以到处跑省去重复开发的成本。另外UniApp 的组件和 API 封装得比较完整比如生命周期、页面跳转、本地存储等都能用统一的方式调用不需要针对每个平台单独处理。我特别看重的一点是社区生态。UniApp 背后有 DCloud插件市场里有很多现成的组件和模板像地图、轮播图、富文本解析这类常见需求都能找到现成的轮子开发效率会高很多。而且遇到问题的时候社区问答和文档相对完善对于比较依赖搜索引擎和社区帮助的开发者来说踩坑后的恢复速度很重要。在实际编写项目的时候用 HBuilderX 作为开发工具配合微信开发者工具一起调试。HBuilderX 对 UniApp 的支持比较到位可以直接运行到微信开发者工具代码变动后热更新也很快。如果使用 Vue CLI 方式创建项目也可以但 HBuilderX 的可视化配置和打包流程对新手更友好能省去不少环境折腾的时间。需要注意的是UniApp 版本迭代比较快官方文档的更新可能滞后所以在写的时候尽量关注发布日志避免使用了已废弃的 API。这点我在项目中期就吃过亏后面会专门在问题排查部分提到。1.2 南昌旅行指南的功能模块拆解整个项目不是把所有功能堆在一起而是在设计阶段先按照旅行场景把功能模块拆清楚然后逐个突破。我梳理了这样几个核心模块目的地信息模块这是旅行指南的底座。包括南昌的城市简介、必去景点列表、景点详情图片、文字介绍、开放时间、门票信息、地址等。这个模块主要解决用户“去哪玩、怎么玩”的诉求。因为数据相对静态可以先用本地 JSON 或远程接口把数据准备好渲染成列表和详情页。行程规划模块根据用户的旅游天数推荐路线比如一日游、两日游、亲子游等。这个模块除了展示推荐路线还可以加入简单的交互比如用户可以收藏某条路线。实际开发时不必做太复杂的算法只需要把路线数据组织好按标签筛选即可。美食推荐模块南昌的美食是旅行体验的重要部分把当地特色小吃、餐馆、人均消费、营业时间、地址等信息集中展示并支持地图定位跳转。这个模块能提升指南的实用感让用户觉得“这不只是景点罗列而是一份真正能用的攻略”。地图导航模块这里不是自己实现地图渲染而是通过手机安装的地图 App 进行导航跳转。UniApp 提供了uni.openLocation接口可以直接打开微信内置地图或者手机地图。这个功能在旅行类小程序里非常重要因为用户真正到了南昌后最常用的就是“我该怎么去这个地方”。地图模块还可以配合列表页做一个“附近景点”排序虽然会麻烦一点但体验会好很多。收藏与笔记模块用户可以对景点、美食、路线进行收藏并且可以添加自己的旅行笔记。这个模块会用到小程序的本地存储或者如果做了用户系统可以保存在后端。收藏功能看似简单却是增强用户粘性的关键也是整个小程序中少有的需要处理状态管理的部分。个人中心模块展示用户信息、我的收藏、我的足迹、意见反馈入口等。如果只是毕设或者演示项目可以不接真实的用户登录而是用微信的uni.login获取 code 换取 openid或者直接用本地模拟用户信息。考虑到小程序审核个人中心里不要放置与核心功能无关的复杂跳转。搜索模块这个模块容易被忽略但实际上很有价值。用户可能知道某个景点名字但在列表页翻半天找不到。提供按景点名称、美食名称的关键字搜索能显著提升使用体验。实现方式也很简单前端对已有的数据进行过滤匹配即可不需要后端参与。在这些模块之上我还规划了 tabBar 导航结构底部放四个 tab首页、攻略、地图、我的。其中“攻略”页聚合景点列表、路线推荐、美食推荐“地图”页直接展示南昌主要景点分布并支持导航跳转“我的”页面承载收藏和笔记。拆解功能模块到这里基本就很清楚了。接下来要考虑的是数据结构设计和项目目录怎么搭这部分直接影响后续开发的顺畅程度。2. 核心细节解析与实操要点2.1 项目目录结构与页面路由设计一个旅行指南小程序页面数量通常在 10 到 15 个之间。如果目录混乱等到做路由跳转和状态管理的时候会非常痛苦。我用 UniApp 的标准结构做了一套适合内容型小程序的目录拆法。常规 UniApp 项目的核心目录有pages、components、static、store、utils。其中pages目录是页面所在的地方但所有页面并不是平铺在同一个目录里的而是按模块继续分子目录例如pages/ ├── index/ │ ├── index.vue // 首页 │ └── search.vue // 搜索页 ├── guide/ │ ├── attractions.vue // 景点列表 │ ├── attraction-detail.vue // 景点详情 │ ├── food.vue // 美食列表 │ ├── food-detail.vue // 美食详情 │ ├── routes.vue // 路线列表 │ └── route-detail.vue // 路线详情 ├── map/ │ └── map.vue // 地图页 └── user/ ├── user.vue // 个人中心 ├── favorites.vue // 我的收藏 └── notes.vue // 旅行笔记这种组织方式的优点是页面之间的关系一目了然后期加页面只需要在对应模块下新建即可不会出现十几二十个页面堆在同一个目录里的情况。路由配置在pages.json文件中UniApp 的路由和微信小程序的 app.json 类似。但我花费最多时间的地方是参数传递。旅行类应用特别依赖列表到详情页的参数传递比如景点列表点击某条数据跳到详情页需要传递景点 id。UniApp 里最常用的方式是 URL 拼接参数uni.navigateTo({ url: /pages/guide/attraction-detail?id item.id })接收页面通过onLoad(options)拿到参数。这里有一个容易踩的坑如果景点 id 是数字 0在有的版本中options.id拿到的可能是字符串0如果直接用全等判断可能有问题。稳妥的做法是拿到参数后统一转成字符串再比较或者在后端查询时用字符串类型处理。还有一种场景是页面间传递复杂对象比如用户点击某个路线后希望详情页直接展示路线数据。不建议把对象序列化成 JSON 字符串放在 URL 上因为 URL 有长度限制而且转义处理很麻烦。正确的做法是把数据放在全局状态里或者使用uni.setStorageSync临时存起来然后在详情页读取。我习惯使用 Vuex 来做页面间共享数据的传递后面会讲到。pages.json 里还需要配置tabBar这里要注意 tabBar 页面的路径不能带参数而且必须出现在pages数组中。颜色和图标风格尽量与旅游主题匹配图标可以到 iconfont 或 DCloud 插件市场找现成的。2.2 公共组件与请求封装在组件拆分上我遵循“不过度设计”的原则。旅行指南项目本身不算大如果一开始就拆分几十个组件反而增加复杂度。我实际拆出来的组件只有这几个search-bar.vue搜索框组件供首页、景点列表页、美食页共用。attraction-card.vue景点卡片组件显示图片、名称、星级、摘要点击后跳详情。food-card.vue美食卡片组件显示图片、名称、推荐理由、人均价格。section-title.vue首页各区块的标题组件比如“热门景点”、“南昌美食”这些区块头部。empty-state.vue空状态组件用于收藏为空、搜索无结果时的提示。这些组件集中在components目录下并通过easycom机制自动引入。UniApp 的easycom非常好用只要组件文件按照规范命名放在components/组件名/组件名.vue目录下页面里无需手动 import 就能直接使用。这减少了开发时重复引入组件的麻烦也保持了页面的代码整洁。请求封装是另一个关键环节。旅行指南的数据来源可以是本地 JSON、远程 API 或者放在云开发数据库里。我这次选择的是远程 API 方式用 UniApp 自带的uni.request做了一层封装主要解决三个问题统一基础 URL 配置开发环境和生产环境切换方便。我在utils/config.js里维护BASE_URL后续如果部署到云服务器只改一个文件即可。统一错误处理和 loading 逻辑。每次请求前自动显示加载状态请求完成后无论成功失败都自动关闭。对返回数据结构做统一校验。我通常会约定后端返回{ code: 0, data: {...}, message: success }这种格式前端只处理code 0的情况非 0 则弹出错误提示。核心封装代码大致是const request (config) { return new Promise((resolve, reject) { uni.showLoading({ title: 加载中... }) uni.request({ url: BASE_URL config.url, method: config.method || GET, data: config.data || {}, header: { Content-Type: application/json, ...config.header }, success: (res) { if (res.statusCode 200 res.data.code 0) { resolve(res.data.data) } else { uni.showToast({ title: res.data.message || 请求失败, icon: none }) reject(res) } }, fail: (err) { uni.showToast({ title: 网络不给力请稍后重试, icon: none }) reject(err) }, complete: () { uni.hideLoading() } }) }) }这里有一个细节值得分享uni.request在微信小程序里默认不支持 cookies所以如果后端需要登录态要手动把 token 放在 header 的Authorization字段中。对于纯演示项目可以不接用户系统但我在开发时仍然留了 token 的注入位置方便以后扩展。数据层方面如果简单场景可以用uni.getStorageSync做本地缓存比如把景点列表缓存下来二次进入时优先走缓存减少请求次数。我实际做了 5 分钟过期时间的缓存机制有效提升了小程序的打开速度。2.3 地图导航与位置服务的实现细节地图页是旅行指南中比较有特色的部分。我并没有在地图页直接渲染地图视图而是用了更轻量的方案在地图页展示景点列表每个景点自带经纬度点击某个景点后调用uni.openLocation打开系统的地图导航界面。这样做的好处是不需要申请地图 SDK 的 key也不需要在页面中嵌入地图组件开发和审核成本都低了不少。核心代码是这样的uni.openLocation({ latitude: 28.682892, longitude: 115.858197, name: 滕王阁, address: 南昌市东湖区仿古街58号, scale: 18, success: () { console.log(打开地图成功) } })这个 API 在微信小程序里会唤起内置地图页面并提供“去这里”的导航按钮。用户可以选择用微信内置地图、高德地图、百度地图或者腾讯地图进行导航体验很顺滑。如果希望在地图页直接展示多个景点的分布可以采用map组件来渲染标记点。UniApp 也封装了map组件在页面中放置map标签然后绑定markers数组即可。不过多标记点的地理位置需要规划好如果景点密集标记点之间会出现重叠需要配合label或callout属性做展示优化。这些细节可以等基础功能做完后再完善。由于用户的位置信息属于敏感权限小程序端获取时会触发用户授权弹窗。在实现“附近景点”功能时我会先调用uni.getLocation获取当前定位然后在地图页根据距离对景点进行排序。这里有几个注意点首次调用uni.getLocation会请求用户授权用户拒绝后再次调用会直接失败需要引导用户到设置页打开授权。真机和开发者工具上的定位表现不完全一致开发者工具里可以模拟位置但真机上更容易出现定位不准或超时的问题。uni.getLocation在高德、腾讯等不同的底图服务上的精度有所差异对于旅游场景误差在 50 米以内都可以接受。我建议在开发时把“附近景点”作为一个可选的增强功能不要在项目初期死磕它先保证核心的景点展示和导航跳转流畅顺畅。2.4 收藏功能的本地存储方案收藏功能表面上很简单就是点个小红心、记录一下但一旦处理不好数据存储和状态同步就会出问题。考虑到项目没有后端数据库我决定使用本地存储来保存收藏数据。思路很直接以用户维度维护一个收藏列表。这里因为不接用户系统所以相当于“本机收藏”。数据结构选择对象数组每个收藏项包含类型景点/美食/路线、标题、图片 URL、ID 和收藏时间。写入时使用uni.setStorageSync(favorites, list)读取时使用uni.getStorageSync(favorites)并做空值判断。具体实现时为了减少存储写入次数我在收藏列表页做了“全选”和“批量删除”功能。批量删除会循环处理选中的数据然后一次性写入本地存储。这个交互设计让用户觉得收藏功能不是摆设工具感更强。收藏状态的同步也值得注意。比如在景点详情页点击“收藏”后如果用户马上返回列表页列表页的收藏按钮状态需要立刻更新。我的做法是使用 Vuex 维护一份favoriteIds映射表并在收藏或取消收藏时提交 mutation 更新 Vuex 状态所有页面读取收藏状态时统一从 Vuex 获取而不是直接从本地存储获取。这样能保证页面间的状态一致性避免出现“收藏了但显示未收藏”的问题。Vuex 状态的持久化则通过监听变化同步到本地存储。简单来说每次 mutation 执行后在方法内部调用uni.setStorageSync把最新状态写进去App 启动时在App.vue的onLaunch里读取本地存储并初始化 Vuex。这种模式在中小型项目中非常实用既保证了实时响应又不会因为频繁读写 storage 导致性能问题。2.5 微信登录与隐私政策合规处理毕设或演示项目经常不需要真正的登录功能但如果想要展示“个人中心有用户信息”又不想搭建后端服务最简单的办法就是调用微信的静默登录uni.login拿到 code 后换取 openid再配合uni.getUserProfile获取用户头像和昵称。不过这个流程里有一个最近很多开发者都会遇到的坑微信小程序如果涉及用户隐私信息需要在平台配置隐私保护指引否则相关接口会调用失败。在我的项目里个人中心一开始使用了uni.getUserProfile来获取用户头像昵称结果在测试时发现按钮点击后没有反应控制台报错提示“隐私政策及用户协议未同意”。问题就出在小程序后台的隐私保护指引没有配置完整。解决方案分为两步第一步在微信公众平台“设置-服务内容声明-用户隐私保护指引”中将项目中用到的用户隐私相关接口全部声明包括chooseAvatar、getUserProfile、getLocation等。第二步在小程序端增加一个隐私授权弹窗。用户在进入“我的”页时如果未同意隐私政策则弹出半屏弹窗展示用户协议和隐私政策摘要点击“同意”后写入本地标记。之后再去调用用户隐私相关接口就不会被拦截。这里我特意把“隐私政策未同意时退出 App”的场景也在代码中做了处理。当用户点击不同意时如果继续停留在页面后续接口会持续报错所以我会显示“需要同意隐私政策后才能正常使用”的提示并提供“退出小程序”按钮调用uni.exitMiniProgram退出。代码大概是这样if (!hasAgreedPrivacy()) { uni.showModal({ title: 提示, content: 请先同意隐私政策后再使用, confirmText: 去同意, cancelText: 退出, success: (res) { if (res.confirm) { openPrivacyModal() } else { uni.exitMiniProgram() } } }) }这算是踩完坑之后沉淀下来的经验后续想扩展用户系统的开发者也值得在项目一开始就把隐私协议流程做好不然后面补会很被动。2.6 分享功能与页面跳转参数处理旅行指南很适合做分享用户看到某个景点觉得不错分享给朋友是自然需求。UniApp 里的小程序分享主要是通过onShareAppMessage生命周期钩子实现的。每个页面里都可以配置分享文案和转发路径。我这里要特别提醒的是一个容易出问题的地方我在多个项目里都遇到过onShareAppMessage被全局方法覆盖的问题。你如果在一个公共组件或全局 mixin 里统一定义了分享回调页面的onShareAppMessage可能会被覆盖掉导致分享标题或图片不生效。尤其是项目里引入了第三方插件或自定义组件时这种事情更加隐蔽。我的解决方案是在每个需要分享的页面里都显式声明onShareAppMessage方法不再依赖全局配置并且在方法里动态设置 title 和 path。比如景点详情页的分享onShareAppMessage() { const attraction this.attraction || {} return { title: attraction.name ? 南昌旅行推荐${attraction.name} : 南昌旅行指南, path: /pages/guide/attraction-detail?id${attraction.id} } }分享出去的 path 要能保证别人打开后落在正确的页面上所以接收端onLoad(options)里需要判断参数是否有 id没有 id 时返回列表页或首页。我通常会加一个兜底逻辑避免分享链接异常导致白屏。还有一点和分享相关微信小程序自带的右上角菜单里的“转发”按钮默认是显示的。如果你不希望某些页面被分享比如个人中心可以在页面的onLoad中通过uni.hideShareMenu隐藏转发菜单。旅行指南里其实所有页面都适合分享所以我没有做隐藏处理但知道有这个能力对后续调整还是很有用的。3. 实操过程与核心环节实现3.1 开发环境搭建与 UniApp 项目初始化正式开始写代码前环境搭建是第一步。我使用的是 HBuilderX 微信开发者工具的组合这两个工具的版本更新频率都不低建议都下载最新稳定版。第一步到 DCloud 官网下载 HBuilderX安装完成后在工具菜单栏选择“文件-新建-项目”。项目类型选择“uni-app”模板我选了默认模板不勾选云开发等附加功能。模板里有默认的 pages/index/index.vue 页面可以先运行起来看看效果。第二步在manifest.json里配置应用信息。这里要重点配置的是“微信小程序配置”一栏包括小程序的 AppID。如果没有 AppID可以点击“测试号”用游客模式运行但有一些 API 在测试号下会有限制比如获取用户信息、支付等。毕设项目建议申请一个自己的小程序 AppID用个人主体免费注册流程不复杂。第三步在 HBuilderX 中选择“运行-运行到小程序模拟器-微信开发者工具”。如果首次运行HBuilderX 会自动要求填写微信开发者工具安装路径以及开启服务端口。在微信开发者工具的“设置-安全设置”中打开服务端口后HBuilderX 才可以自动拉起项目。这一步经常有小伙伴卡住建议对照官方文档仔细检查。项目首次编译成功后微信开发者工具里就能看到默认的首页。这时候开发环境就算跑通了。接下来建议把 pages.json 里的基础导航样式、全局颜色、tabBar 图标先配好因为后面页面多起来后再回头改全局配置容易出现样式不一致的问题。导航标题的字体、颜色可以统一在pages.json的globalStyle中配置。我遇到过一个顶部导航栏和内容区重叠的问题原因是navigationStyle被改成了custom所以页面顶部要手动加上安全距离。旅行指南这类应用其实不需要自定义导航栏维持系统默认的导航样式即可省掉一大波适配精力。3.2 页面开发顺序与核心页面实现细节在实际开发中我建议按照“首页 → 列表页 → 详情页 → 地图页 → 个人中心”的顺序推进。这样每个阶段都有可视化的成果不会出现写了好久什么都看不到的挫败感。首页的重点是区块布局和导航入口。页面顶部是搜索框组件中部是轮播图展示南昌城市风貌下方依次是热门景点、今日路线、南昌美食三个区块。每个区块的标题都带“更多”链接点击跳到对应的列表页。轮播图我用的是 swiper 组件这个组件在小程序里经常会遇到图片懒加载的问题。我通过lazy-load属性开启懒加载并且给每张图片设置合适的宽高避免图片加载时页面跳动。需要注意的是swiper 里的图片路径最好使用网络图片或已压缩的本地图片本地图片过大会导致小程序包体积膨胀。景点列表页采用上拉加载更多的交互方式。数据结构里本来就包含景点列表为了演示分页逻辑我把数据在前端做了切片处理每页加载 6 条。滚动到底部时触发加载函数将下一页的数据追加到当前列表。这种前端分页方式虽然没有后端分页真实但能完整展现分页思路对后续接入真实接口很友好。详情页的核心是图文混排。景点详情不仅是段落文字还包含多张图片、开放时间表格、交通方式、周边推荐等。这里我使用了rich-text组件来渲染后端返回的富文本内容但要注意rich-text中不能直接绑定点击事件。如果详情里包含需要跳转的链接比如外部路线链接建议还是用普通的 view 组件自己布局不要依赖富文本。地图页我采用了列表 标记点聚合的方式。页面顶部是一个地图组件下方是景点列表点击列表项会在地图上高亮对应的标记点同时弹出卡片展示简要信息。地图组件在开发者工具中的渲染和真机上有差异真机上的标记点文字大小和图标比例需要反复调我最后用 CSS 固定了标记点callout的样式真机表现才稳定下来。个人中心页相对简单除了用户信息区和功能入口外我把“隐私政策”和“关于本项目”放在了列表底部点击后跳转到对应的 web-view 或本地页面。这里 web-view 加载外部网页时需要配置业务域名个人小程序无法随便添加外部域名所以我把“关于”写成了本地静态页面省去了域名配置的麻烦。3.3 数据组织与远程接口设计旅行指南的数据内容非常固定我建议在做页面之前先把数据模型定义清楚。以景点为例我定义的字段包括id唯一标识name景点名称cover封面图 URLimages详情页图集summary一句话简介content详细介绍openTime开放时间ticket门票信息address地址latitude/longitude经纬度category分类如历史古迹、自然风光、文化场馆rating评分用于排序展示tags标签数组如“南昌地标”“必游”“亲子友好”美食数据同理额外增加recommendDishes字段推荐菜品以及avgPrice人均消费。数据集我初期放在static/data/attractions.json、food.json、routes.json中开发调试时直接 import 或通过uni.request读取本地 JSON。等到后期需要演示前后端交互时再把这些 JSON 数据挂到一个简单的后端服务上前端通过封装好的请求方法访问即可。如果使用微信云开发那么数据可以放到云数据库用wx.cloud.callFunction来读取。但我这版考虑到部署成本和 SQL 操作习惯还是选择了传统 HTTP 接口方式使用 uniCloud 或自己搭的 Node.js 服务。接口的状态码我统一约定为0成功1参数错误2未登录3服务器错误。前端请求封装里对0之外的状态码做了统一错误 toast这样后端逻辑出来了前端也不会乱。这里分享一个数据渲染的常见坑小程序对 JS 对象中 undefined 字段的渲染并不报错但页面可能显示空白。比如详情页某条数据没有ticket字段直接渲染{{ attraction.ticket }}会显示为空白体验很不友好。所以我会在页面加载时对数据进行初始化确保每个字段都存在默认值this.attraction { ticket: 免费, openTime: 全天开放, tags: [], ...data }这样即使某个景点的数据暂时缺失页面也能正常展示不会出现异常空白区域。3.4 微信小程序发布与审核前自检小程序开发完成后最关键的环节是发布审核。如果你不使用“测试号”而是用正式 AppID就需要走一次完整的上传和提审流程。提审前有几个容易出问题的点第一微信小程序要求所有请求的接口域名必须配置为 HTTPS并且在后台“服务器域名”中添加 request、downloadFile 等合法域名。测试阶段可以勾选开发者工具中的“不校验合法域名”但正式版本必须配置真实域名否则接口全部请求失败。第二小程序代码包主包大小限制为 2MB总体积可以通过分包扩展到 20MB。旅行指南如果使用了大量高清图片很容易超出 2MB 限制。我在项目里把图片全部放到图床或者云存储上本地只保留极少量的占位图这样能有效控制包体积。如果依然超出可以用“分包加载”的方式把美食模块和路线模块做成独立分包主包只保留核心页面。第三隐私政策弹窗必须和真实使用的接口保持一致不要自作聪明地去填写无关的接口否则审核会以“隐私政策与实际功能不符”为由驳回。我在项目里就是因为一开始漏掉了位置信息声明被驳回过一次后来补充完整才通过。第四小程序名称不能使用“南昌旅行指南”这类看起来像官方服务的名称容易被官方平台拦截或者被判定为蹭地域名称。所以我在注册时改成“南昌旅游助手”一类更有识别度、也更合规的名字这里在开发前就要考虑好不然发布阶段再来想名字会被迫改很多内容。顺利通过审核后小程序就可以在微信中被搜索到和访问了。整个发布流程虽然繁琐但只要按官方文档逐步走不会出现不可逾越的障碍。4. 常见问题与排查技巧实录4.1 页面样式在不同机型上错乱这是我调试过程中花费时间最多的问题。比如同一个详情页在 iPhone 上表现正常在安卓真机上标题栏却出现偏移或者在开发者工具里很完美在真机上图片却变形。排查思路是先分清是布局问题还是组件问题。布局问题通常出在vh、vw、rpx的混用上。小程序里rpx是自适应单位750rpx 等于屏幕宽度大多数场景用它做间距和尺寸单位都很稳。但如果涉及到高度尤其是一屏显示和滚动区域100vh和100%的表现不一样需要根据场景选择。另一个常见坑是底部安全区域。iPhone X 之后全面屏手机都有底部小黑条页面内容会被遮挡。我通过env(safe-area-inset-bottom)来处理padding-bottom: constant(safe-area-inset-bottom); padding-bottom: env(safe-area-inset-bottom);这个技巧尤其适合个人中心、详情页底部操作栏这类贴近底部的布局。4.2 地图组件和 swiper 组件在 iOS 上的层级问题我在做详情页时在swiper组件里嵌入了video组件结果在 iOS 上出现视频全屏播放后错位、退出全屏后页面布局混乱的问题。这是微信小程序的经典 bug原生组件的层级远超普通组件video和map都属于原生组件。解决方案有两种。一种是不在 swiper 里放 video改为点击图片后弹出视频层另一种是使用cover-view和cover-image来覆盖原生组件。cover-view是专门用来覆盖在原生组件之上的组件比如在地图上绘制自定义按钮、在视频上叠加标题等。我在旅行指南里没有直接使用视频所以这个问题只在调试时遇到过但值得记录。真机调试时原生组件的渲染层级问题非常影响体验在代码审查阶段一定要检查页面中是否有map、video、canvas这些组件并确保自定义的覆盖元素使用cover-view否则调试时很崩溃。4.3 onShareAppMessage 被全局方法覆盖前面已经提过这个坑这里再多说一点细节。如果一个页面引用了公共组件组件内部如果定义了onShareAppMessage或类似的生命周期方法可能会覆盖页面自身的分享配置最终导致分享出去的链接和标题不符合预期。有一次我把分享方法写在了一个公共 mixin 中期望所有页面默认都有分享能力结果详情页的自定义分享标题完全不生效。排查时发现公共 mixin 被components里的某个组件间接引用了生命周期覆盖优先级超出了我的预期。最终的解决方法是所有页面的分享逻辑都放在页面级onShareAppMessage中公共 mixin 只处理通用逻辑不设置path和title。这样虽然代码重复了一点但可预期性和可维护性都更强。4.4 软键盘遮挡输入框与查询内容在搜索页和笔记编辑页我都遇到过软键盘弹出后遮挡底部内容的问题。微信小程序输入框聚焦时软键盘会从底部弹出如果输入框位置靠下很容易被遮挡用户看不到输入内容。这个问题的根源是输入过程中页面没有自动滚动到可视区。我在项目中用了两个方法配合解决页面底部留出adjust-position空间。input组件默认adjust-position为true微信会尝试自动上推页面但有时效果不好我关闭了它改为监听键盘高度变化手动调整页面位置onKeyboardHeightChange(e) { this.keyboardHeight e.detail.height }在输入框获取焦点时使用uni.pageScrollTo将输入框滚动到可视区域的中间位置。uni.pageScrollTo({ selector: #search-input, duration: 300 })这个方法实测在搜索页非常有效。iOS 上也需要注意uniapp中 H5 输入框自动上顶的问题如果使用adjust-position无效就需要用pageScrollTo手动控制。4.5 接口请求失败排查清单旅行指南里的远程接口在开发环境跑得好好的一上线就失败的情况很常见。我整理了一份自己的排查清单供参考是否勾选了“不校验合法域名”开发者工具里勾选可以跳过域名校验但真机预览和正式版必须关闭。小程序后台的服务器域名是否配置正确注意request合法域名和downloadFile合法域名是分开配置的。接口返回的Content-Type是否正确如果后端返回了text/htmluni.request解析 JSON 可能会失败。证书是否有效过期证书会直接导致请求失败。是否被防盗链拦截如果图片服务限制了 referer在小程序里加载图片时会 403可以在后台配置或使用 CDN。这些看起来都是小问题但每一个都能让你在联调时耗上半天时间。把清单保存下来每次都按顺序过一遍效率提升非常明显。4.6 打包上架安卓应用市场的补充经验虽然项目核心是微信小程序但既然 UniApp 支持多端我顺势把同一套代码打包成了安卓 App。在打包新闻资讯、旅行工具这类纯内容应用时尾部 3 个入口尤其重要隐私政策弹窗、用户协议、以及应用权限说明。安卓打包流程很简单HBuilderX 里选择“发行-原生 App-云打包”填入安卓证书信息后即可在线打包。我遇到的主要问题是软件著作权和隐私政策声明不符合应用市场的要求导致被驳回。后来我在项目打包前就把隐私政策页做成了独立静态页面并在 App 首次启动时弹窗展示用户同意后才会进入主界面这和应用市场的审核要求完全一致。另外如果小程序里用了uni.login在打包成 App 后这个接口会调用 uni 的登录能力需要在manifest里配置对应的 appid 和密钥否则登录功能会失效。这也是很多人打包后才发现的坑。5. 扩展优化建议5.1 从本地数据过渡到微信云开发如果想把旅行指南做成一个可以长期维护的项目数据不应该停留在本地 JSON 阶段。推荐用微信云开发因为它的数据库和存储对小项目免费额度够用而且和微信生态集成很好。数据直接通过前端调用uniCloud或wx.cloud读取后端逻辑写云函数部署也方便。云开发的另一个优势是不需要自己准备服务器和域名天然就跑在 HTTPS 下天然满足小程序的请求域名要求。对于没有自己的服务器、但想把毕设展示得“真像那么回事”的同学云开发是首选。切换数据层时接口结构可以保持不变前端只修改请求封装里的 baseUrl 即可。这样页面代码完全不用动迁移成本很小。5.2 引入用户系统和评论功能旅行指南如果有用户系统体验会完整很多。用户可以在景点详情页打分、写评论也可以记录自己的旅行足迹。评论数据建议放在云端数据库通过景点 id、用户 id 做关联查询。用户系统接入时最容易踩的坑是登录态过期和小程序新版本的隐私政策。个人开发者身份的小程序可使用手机号快捷登录但这部分能力也需要配置。如果你的目标只是演示使用uni.login获取 code 后在后端换取 openid 就够了不必实现完整的注册登录流程。5.3 内容运营与数据更新旅行指南的内容时效性很强景点开放时间、门票价格随时可能变化。如果数据写死在前端每次更新内容都要发布新版本非常不便。建议把数据管理放到一个简单的后台可以是云开发的数据库管理页面也可以做一个简单的 admin 页面运营人员可以直接在后台编辑景点信息。我在项目里加了一个“数据更新时间”字段首页展示“信息更新于某月某日”既能增加信任感也给运营侧一个督促更新的机制。这个小细节在演示时特别加分。6. 项目演示与上线要点6.1 演示准备清单毕设答辩或项目展示之前我建议提前准备一份演示脚本按照一个游客的视角走完整个流程打开小程序展示首页轮播图和景点推荐。点击某个热门景点进入详情页看图文介绍和门票信息。点击地图按钮跳转导航界面展示如何使用位置服务。浏览“美食推荐”查看一家网红老店。收藏一个景点进入个人中心查看收藏列表。点击分享展示自定义分享卡片效果。如果是远程演示建议同时打开微信开发者工具的“真机调试”让评委可以实时扫码体验。提前测试好所有跳转避免演示现场出现页面空白或者接口超时。6.2 上线审核时的材料准备正式提审前需要准备的材料包括小程序名称、头像、简介。简介里明确说明这个是个人学习项目或虚拟内容不要出现“官方”、“政府”等字眼。服务器域名配置截图。隐私保护指引。如果需要用到地图能力在“接口权限”里开启对应的权限声明。提审后平台一般会在 1-7 天内反馈如果被驳回及时查看驳回原因并修改重新提交。大部分驳回原因都是隐私政策、类目选择、内容合规性问题仔细对照规则就不会卡太久。我在实际项目里最耗时的是小程序名称审核因为一开始想用“南昌旅行指南”结果提示名称含有地域名需要提供相关资质个人开发者无法通过。后来改成“南昌旅行手册”一次性通过。这个细节如果提前想清楚能节省不少时间。7. 写在最后的实践心得旅行指南这类内容型小程序技术上并没有多高的难度真正的挑战在于把内容组织得有条理、把交互做得顺手、把审核流程走稳。我做完这个项目最大的收获不是某个 API 用得多熟练而是建立了一套从需求拆解到前端实现、再到上线审核的完整思路。如果你是用它来交课程作业或者毕业设计建议在功能之外多展示一些设计思考比如数据模型怎么设计的、页面状态怎么同步的、隐私合规是怎么考虑的这些才是答辩老师更关心的部分。纯粹罗列功能点很容易显得像在“堆功能”但当你能够把每个功能背后的取舍讲清楚整个项目的档次就完全不一样了。最后再分享一个小技巧在个人中心放一个“清空缓存”按钮对测试调试和答辩演示都非常有用。点一下就能重置收藏、清空历史记录、恢复初始状态省去手动删数据的麻烦也让每次演示都干干净净的。这个小功能实现起来只要几行代码但实际体验提升非常明显。
返回列表