尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

原生微信小程序实战:从艺术展览源码看页面路由与模板复用

原生微信小程序实战:从艺术展览源码看页面路由与模板复用 简介面向微信小程序开发者与前端学习者的艺术展览类项目工程包主要解决展览信息分散、现场购票排队、观展导览不便等场景痛点适合作为小程序快速搭建与功能复用的参考。资源共包含75个文件压缩后仅802KB文件类型覆盖JS逻辑、JSON配置、WXML与WXSS页面结构、PNG/JPG图片素材以及DB数据文件目录按照基础配置、页面模块、公共组件、工具函数和图片素材划分便于开发者快速定位。已有180人学习浏览。工程实现了展览信息展示、电子票务、艺术品详情、地图导览、评论互动、在线商城、艺术教育等功能并集成微信登录、支付、分享等开放能力同时呈现离线缓存、动态更新、轻量化运行等小程序关键技术特点。通过该工程开发者可以迅速搭建文化展览类小程序理解页面结构、数据绑定与组件化开发思路也能为其他行业小程序项目提供可复用的设计参考。1. 从一份可运行的艺术展览小程序源码说起拆开这个压缩包之前我以为它只是又一个课程设计模板打开后反而花了半小时细看pages、utils、template 一个不少首页、展品详情、二维码、发现、设置、登录、注册七个页面都给了完整逻辑而且用的是原生微信小程序写法不是 uniapp 或 Webview 套壳。对刚跑过官方 demo 的开发者来说这份源码最大的价值在于它把艺术展览类小程序最常见的链路串了起来首页加载展览列表、模板渲染展品卡片、扫码跳详情、登录之后再进个人中心。不管你是拿它做毕业设计还是接手公司里一套原生小程序需要找参照都能照着这份微信小程序项目实例理解页面路由、请求封装和组件复用。2. 页面路由与全局配置app.json 才是骨架在微信小程序项目里app.json 决定了哪些页面被编译、底部导航长什么样、窗口标题用什么颜色。这份艺术展览源码里pages 目录下共有 index、work-detail、qrcode、discover、setting、register、login 七个页面对应的 app.json 大致如下。{ pages: [ pages/index/index, pages/discover/discover, pages/work-detail/work-detail, pages/qrcode/qrcode, pages/setting/setting, pages/register/register, pages/login/login ], window: { navigationBarTitleText: 艺术展览, navigationBarBackgroundColor: #1A1A1A, navigationBarTextStyle: white }, tabBar: { color: #8A8A8A, selectedColor: #B08D57, backgroundColor: #FFFFFF, list: [ { pagePath: pages/index/index, text: 首页, iconPath: images/bar_home.png, selectedIconPath: images/bar_home_black.png }, { pagePath: pages/discover/discover, text: 发现, iconPath: images/bar_explore.png, selectedIconPath: images/bar_explore_black.png }, { pagePath: pages/setting/setting, text: 我的, iconPath: images/bar_mine.png, selectedIconPath: images/bar_mine_black.png } ] } }pages 数组第一项就是刚进入的加载页面也就是首页如果你想把启动页临时换成 qrcode直接调整数组顺序即可。tabBar 的 list 最多五项这里配置了三项首页、发现、我的分别对应首页展示、发现页浏览展讯和个人中心。需要注意 tabBar 的 iconPath 与 selectedIconPath 只支持本地图片路径不能填 http 链接所以压缩包里的 bar_home、bar_explore、bar_mine 这些 png 就是给这里用的。为了快速梳理页面职责我按文件列表整理了一张表。页面路径职责关键资源pages/index/index展览列表/首页images/home_title.png、location.pngpages/discover/discover发现页images/discover、images/ditu.pngpages/work-detail/work-detail展品详情images/aa1.jpg、eye.pngpages/qrcode/qrcode二维码入场images/ic_triangle.png、qrcodepages/setting/setting个人设置images/bar_mine_black.pngpages/register/register注册账号images/we.pngpages/login/login登录images/dianhua.jpg、kefu.jpg2.1 index 首页的工作台onLoad 到 setDataindex 页面的逻辑写在 app.js 之外由 pages/index/index.js 独立管理。艺术展览小程序首页一般需要请求展览列表拿到数据后再渲染。下面是符合这套源码结构的常见写法。const api require(../../utils/api.js) Page({ data: { exhibitions: [], loading: true }, onLoad() { this.fetchExhibitions() }, fetchExhibitions() { api.request(/exhibitions, GET).then(res { this.setData({ exhibitions: res.data, loading: false }) }).catch(() { this.setData({ loading: false }) }) } })这段代码把请求和渲染拆开onLoad 只负责调用 fetchExhibitions网络返回后通过 setData 更新 exhibitions 和 loading。setData 是页面数据层和渲染层唯一的通信通道频繁调用会影响性能所以这里只在请求结束后一次性更新。loading 的初始值是 true配合 wxml 里的 wx:if 控制加载态。2.2 动态设置标题让每个页面自己改导航栏很多从后台拿数据的页面不能用静态标题比如展品详情页要显示“夏加尔真迹展览”而不是“艺术展览”。原生小程序提供了 wx.setNavigationBarTitle。wx.setNavigationBarTitle({ title: 夏加尔真迹展览 })调用时机建议放在 onLoad 里这样页面一打开就能看到正确标题。这里有个坑如果 app.json 中 window 配置了 navigationStyle: custom自定义导航栏模式下 wx.setNavigationBarTitle 不会生效需要自己渲染标题区域。另外H5 页面修改 document.title 的方式在小程序里完全不可用必须走这个 API。2.3 页面跳转navigateTo、redirectTo 和 switchTab 的边界艺术展览小程序里从首页点进展品详情用的是 wx.navigateTo它会保留当前页面栈从首页切换到“我的”tab 必须用 wx.switchTab如果登录成功后不想让用户返回登录页用 wx.redirectTo 替换当前页面。wx.navigateTo({ url: /pages/work-detail/work-detail?id1001 }) wx.switchTab({ url: /pages/setting/setting }) wx.redirectTo({ url: /pages/index/index })navigateTo 的页面栈最多十层超过之后调用会失败。所以长流程页面里应避免连续跳转必要时用 redirectTo 减少栈深度。这是原生小程序和多端框架 uniapp 差异比较大的地方uniapp 的 uni.navigateTo 底层仍然是这套页面栈但有些开发者会误以为框架替自己做了栈管理。另一个容易混淆的点是页面栈与 tabBar 的关系tabBar 页面之间切换不会互相销毁首次进入某个 tab 页面时触发 onLoad之后切换只触发 onShow。所以首页的 onLoad 里只做一次初始化onShow 里做数据刷新否则从登录页返回时会看到旧数据。3. 模板复用item-template.wxml 如何撑起展品列表压缩包里 template 目录下只有一个 item-template.wxml但首页和发现页都引用了它。这个设计看似简单实际解决了艺术展览这类内容型小程序最头疼的问题同一个展品卡片不能只写一份 wxml否则每增加一个入口就要复制一遍。3.1 template 的 name 与 data 传递模板文件里通过 定义一个名为 work-card 的组件。它的 data 由外层传进来模板内部不能直接调用页面方法事件要通过 bindtap 冒泡到宿主页面。template namework-card view classwork-card bindtaponWorkTap>import src../../template/item-template.wxml / block wx:for{{exhibitions}} wx:keyid template iswork-card data{{...item}} / /block这里的data{{...item}}是 WXML 的解构写法把 item 中的 cover、title、author、tags 等字段分别传给模板。如果写成直接传整个 item模板内部就需要改为引用 item.cover可读性会变差。is 属性指定模板名和 对应。discover 页如果要做瀑布流或两列布局引用同一个模板外层包一个 view 设置列宽即可。模板里的 bindtap 事件依然指向宿主页面的 onWorkTap所以两个页面都需要实现这个函数。3.3 wx:elif 处理加载态和空态艺术展览小程序进入首页时如果接口慢或者没有展览不能直接让页面白屏。原生小程序的条件渲染最适合处理这种场景。view wx:if{{loading}}正在加载展览数据.../view block wx:elif{{exhibitions.length 0}} block wx:for{{exhibitions}} wx:keyid template iswork-card data{{...item}} / /block /block view wx:else暂无展览去看看发现页吧/viewwx:if、wx:elif、wx:else 必须连续使用中间不能插入其他节点。loading 为 true 时只显示加载文案不渲染列表loading 为 false 且列表非空时渲染模板否则显示空态。这里没有用 hidden是因为 wx:if 会从渲染树中移除节点在长列表场景下可以减少不必要的首屏渲染时间代价是切换时需重建节点。需要说明的是template 适合纯展示型复用如果展品卡片需要独立的生命周期或私有样式隔离应该改用 Component 自定义组件。这个项目的定位是轻量艺术展览应用用 template 已经足够。另外如果 tags 为空数组block 不会渲染任何内容但模板外层最好预留固定高度否则加载完成后卡片会明显跳动。4. 登录链路与 API 封装utils 目录下的工程化细节utils 目录里有 es6-promise.min.js 和 api.js这两份文件决定了整个小程序请求登录链路是否稳定。很多新手拿到源码后直接跑页面报错往往从这里开始。4.1 es6-promise.min.js 解决了什么问题微信小程序的基础库在早期版本里不提供完整的 Promise 实现或者在某些 Android 机型上表现不一致。源码里把 es6-promise.min.js 放在 utils 目录就是要在加载 api.js 之前先把 Promise 打上补丁。在模块顶部这样引入const Promise require(./es6-promise.min.js)如果你在开发者工具的控制台里看到 “Promise is not a constructor” 这类错误一般就是基础库版本太老或者代码中在 require 之前使用了 Promise。把这个 polyfill 放在所有请求模块的依赖头部比在 app.js 里全局挂载更方便维护。当然现在新版微信开发者工具默认基础库都支持 Promise这行代码更多是为了兼容旧版本真机。4.2 api.js 统一请求封装与鉴权头api.js 的核心是把 wx.request 包成 Promise同时把 token 注入请求头。艺术展览小程序里用户登录后获得 token后续所有接口都靠它识别身份。const Promise require(./es6-promise.min.js) const BASE_URL https://api.example.com function request(path, method GET, data {}) { return new Promise((resolve, reject) { const token wx.getStorageSync(token) wx.request({ url: BASE_URL path, method, data, header: { Content-Type: application/json, Authorization: token ? Bearer ${token} : }, success(res) { if (res.statusCode 200 res.statusCode 300) { resolve(res.data) } else if (res.statusCode 401) { wx.navigateTo({ url: /pages/login/login }) reject(res) } else { wx.showToast({ title: (res.data res.data.message) || 请求失败, icon: none }) reject(res) } }, fail: reject }) }) } module.exports { request }参数说明表如下。参数类型说明pathstring接口路径如 /exhibitionsmethodstringGET / POST / PUT / DELETE默认 GETdataobject请求参数GET 时会拼到 queryheader.AuthorizationstringBearer token无 token 时为空字符串success 回调function只处理 2xx 状态码401 处理无清除 token 并跳登录页成功后返回的是整个响应体 res.data调用方通过 then 拿到业务数据。这里刻意没有对 res.data.code 做判断因为不同后端接口的错误格式不统一统一在业务层处理反而更容易踩坑。如果项目里后端固定返回 { code, message, data }建议在 Promise resolve 之前先判断 code 是否为 0非 0 直接 reject。4.3 登录注册页面的流程编排register 和 login 两个页面是配套的注册完成后通常会自动带出用户名再走登录接口。下面是登录页提交逻辑的常见写法。// pages/login/login.js const api require(../../utils/api.js) Page({ data: { username: , password: }, submit() { const { username, password } this.data if (!username || !password) { wx.showToast({ title: 请输入账号和密码, icon: none }) return } api.request(/auth/login, POST, { username, password }).then(res { wx.setStorageSync(token, res.data.token) wx.setStorageSync(userInfo, res.data.userInfo) wx.navigateBack() }) } })登录成功后用 wx.setStorageSync 同时保存 token 和用户信息然后 navigateBack 返回上一个页面。这里不建议用 wx.redirectTo因为用户可能从个人中心被踢到登录页登录成功后需要回到原页面navigateBack 会保留页面栈。一个很常见的坑出现在开发者工具上点击预览时提示“登录用户不是该小程序的开发者”。这个报错和代码逻辑无关是当前微信号没有加入项目的开发者权限。解决方法是到微信公众平台后台成员管理里添加该微信号或者让管理员在开发者工具中扫码确认。4.4 二维码页面从扫码到落地页qrcode 页面承担入场凭证和作品导览两个能力。一种流程是自己生成二维码另一种是扫别人发的码跳进来。如果是从外部扫码进入小程序会通过 wx.scanCode 拿到原始内容。wx.scanCode({ success(res) { const path res.path || res.result if (path path.startsWith(/pages)) { wx.navigateTo({ url: path }) } else if (path !path.includes(://)) { wx.navigateTo({ url: /pages/work-detail/work-detail?target encodeURIComponent(path) }) } } })res.path 是扫码得到的已发布小程序页面路径比如 pages/work-detail/work-detail?id1001此时可以直接 navigateTo。res.result 是原始字符串大多数情况是 URL 或自定义协议内容需要先判断再决定跳转方式。encodeURIComponent 是为了防止参数里有 或 把 query 截断如果你发现 get 参数里的等于号变成了 %3D就是这个位置没有做编码。扫码进入落地页时微信还会校验二维码内容是否与小程序绑定普通 URL 二维码默认不会直接拉起小程序。需要在公众平台生成小程序码才能保证 res.path 正确返回。如果没有生成建议引导用户手动搜索小程序名称。5. 适配与排错导航栏高度和图片资源那些事最后不做什么宏大总结直接讲几个实操时最容易影响观感的小点这些都是看这份压缩包时顺手发现的问题。5.1 自定义导航栏的高度计算很多艺术展览页面为了沉浸式展示海报会在 app.json 的 window 里配置 navigationStyle: custom这时顶部导航栏高度就不能再用 64px 这样的固定值。iPhone 有刘海、Android 各家状态栏高度不同正确做法是拿到胶囊按钮位置来推算。const menu wx.getMenuButtonBoundingClientRect() const system wx.getSystemInfoSync() const navBarHeight (menu.top - system.statusBarHeight) * 2 menu.height const totalHeight navBarHeight system.statusBarHeightmenu.top 是胶囊按钮离屏幕顶部的距离system.statusBarHeight 是状态栏高度两者差值再乘 2 加胶囊高度就是导航栏内容的实际高度。totalHeight 用于设置自定义导航栏容器的 padding-top。这个计算在 Android 和 iOS 上表现基本一致但要注意不能在 onLoad 里同步拿数据建议放在 onReady 之后。5.2 图片资源的体积治理压缩包 images 目录里混入了 Thumbs.db这是 Windows 生成的缩略图缓存文件放在小程序包里不仅没有意义还会白白增加包体积。发布前可以用下面命令清理。find . -type f -name Thumbs.db -delete这个命令递归删除当前目录下所有 Thumbs.db。注意在 macOS 或 Linux 下执行没风险Windows 下直接删除可能会被占用导致失败可以先关闭文件资源管理器。另外images 里的 jpg 和 png 建议用 TinyPNG 压缩艺术展览类小程序图片多主包体积超过 2MB 后无法直接真机预览。5.3 用抓包工具定位接口问题有时候首页列表加载不出来代码看了一遍也没发现错那就需要抓包。以 Charles 为例电脑和手机连同一局域网手机设置 HTTP 代理指向电脑的 8888 端口再安装 Charles 的根证书并开启 SSL Proxying 把 api.example.com 加进去就能看到微信小程序的请求细节。如果抓不到 HTTPS 请求排查顺序是证书是否信任、目标 host 是否在 SSL Proxying 列表、小程序是否开启了不校验合法域名。真机上调试时开发者工具右上角详情-本地设置里可以勾选不校验合法域名但发布前必须把接口域名加到微信公众平台后台的 request 合法域名里。这里补一个常见误用有些人想通过 weixin://dl/business 这类链接在外部唤起小程序直接放在业务链接里。此类跳转需要后台生成对应 URL Scheme 或小程序 Link且必须配置业务域名单纯在开发者工具里用 wx.navigateTo 访问 weixin:// 协议是不会生效的。真机扫码验证时以微信客户端实际行为为准。本文还有配套的精品资源点击获取
返回列表