☰
微信小程序火车票查询实战:从请求封装到列表渲染
2026/10/20 14:10:04 网站建设 项目流程

简介:这份资源是一套基于微信小程序实现的火车票查询系统源码,面向具备小程序基础语法、希望练习接口调用与页面联调的开发者,也可作为课程设计或毕业设计的起步模板。项目围绕车次查询、站点信息与余票展示等典型场景组织代码,方便在此基础上继续扩展订单、登录等功能。压缩包共23个文件,约11KB,包含8个js逻辑脚本、5个json配置、5个wxss样式、4个wxml页面结构以及1个md说明文档,分别承担业务逻辑、页面配置、界面样式与视图渲染等职责,目录按页面与工具模块划分,结构清晰。目前已有1643人学习下载。读者可从中获取完整的小程序请求封装与页面交互示例,理解api与request工具模块的调用方式,并参考其目录组织与配置写法,快速搭建自己的查询类小程序,减少从零摸索的成本。

1. 从一次抢票失败说起:这个火车票查询小程序到底能干什么

去年春运前,我帮朋友看一个抢票脚本的问题,结果发现他连车次余票都查不明白——用的还是某个第三方网页,接口返回的数据结构一变,整个脚本就崩了。当时我就想,与其依赖别人维护的接口,不如自己写一个能跑通查询链路的小程序。后来在代码仓库里翻到了这个「微信小程序-火车票查询」项目,它正好解决了一个很具体的需求:在微信里输入出发站、到达站和日期,直接拿到车次列表和余票信息,不用跳来跳去。

这个项目适合两类人:一是想学小程序网络请求和列表渲染的新手,代码结构简单,跑起来就能看到数据;二是需要快速搭一个查询工具原型的开发者,核心逻辑抽出来就能复用。它不涉及支付和下单,只做查询展示,所以调试成本很低,不用申请复杂的商户资质。接下来我按「能跑、能看懂、能改」的顺序,把这个项目的骨架和关键实现拆一遍。

2. 项目骨架与运行链路:从目录结构到第一次请求成功

2.1 目录结构:小程序标准分层与页面组织

拿到源码后,先看根目录。这个项目用的是微信小程序原生框架,没有引入第三方 UI 库,所以目录很干净。常见结构是pages放页面、utils放工具函数、app.json做全局配置。我打开时第一眼找的是app.json里的pages数组,它决定了启动页和页面路径。

{ "pages": [ "pages/index/index", "pages/result/result" ], "window": { "navigationBarTitleText": "火车票查询", "navigationBarBackgroundColor": "#1E90FF" } }

这段配置里,pages数组第一项是启动页,所以打开小程序先看到查询表单。window里设了导航栏标题和背景色,改颜色直接改十六进制值就行。注意pages路径不要写.js后缀,否则编译会报错,这是新手常踩的坑。

pages/index负责输入,pages/result负责展示车次列表。两个页面之间用wx.navigateTo跳转,参数通过 URL 拼接传递。这种拆分方式让查询逻辑和渲染逻辑分开,改起来不会互相干扰。

2.2 查询表单:输入校验与日期选择

首页的index.wxml里放了两个输入框和一个日期选择器。出发站和到达站用<input>,日期用<picker mode="date">。我一般会在这里加一个非空校验,防止用户直接点查询导致请求参数为空。

// pages/index/index.js Page({ data: { from: '', to: '', date: '' }, onFromInput(e) { this.setData({ from: e.detail.value.trim() }); }, onDateChange(e) { this.setData({ date: e.detail.value }); }, onQuery() { const { from, to, date } = this.data; if (!from || !to || !date) { wx.showToast({ title: '请填写完整', icon: 'none' }); return; } wx.navigateTo({ url: `/pages/result/result?from=${from}&to=${to}&date=${date}` }); } });

setData是小程序更新视图的核心方法,这里把输入值同步到data。onQuery里先做空值判断,再用模板字符串拼 URL。注意from和to如果包含特殊字符,需要encodeURIComponent处理,否则跳转后参数会截断。日期选择器返回的是YYYY-MM-DD格式,正好符合大多数查询接口的要求。

2.3 网络请求封装:wx.request 与公共配置

查询请求放在result.js的onLoad里。项目里把请求域名和路径抽到了utils/config.js,这样换接口时不用翻遍所有页面。

// utils/config.js const BASE_URL = 'https://api.example.com'; // 替换成实际可用的查询接口 module.exports = { QUERY_TRAINS: `${BASE_URL}/train/query` };
// pages/result/result.js const config = require('../../utils/config.js'); Page({ data: { trains: [], loading: true }, onLoad(options) { const { from, to, date } = options; this.queryTrains(from, to, date); }, queryTrains(from, to, date) { wx.request({ url: config.QUERY_TRAINS, method: 'GET', data: { from, to, date }, success: (res) => { if (res.data && res.data.code === 0) { this.setData({ trains: res.data.list, loading: false }); } else { wx.showToast({ title: '查询失败', icon: 'none' }); } }, fail: () => { wx.showToast({ title: '网络异常', icon: 'none' }); } }); } });

wx.request的data字段会自动拼成查询字符串,不用手动拼接。success里先判断业务状态码,再取list赋值给trains。这里有个细节:如果接口返回的字段名不是list,需要同步改setData的键名。fail回调处理网络层错误,比如域名未配置或超时。注意小程序正式环境要求 HTTPS 域名,开发时可以在开发者工具里勾选「不校验合法域名」临时绕过。

2.4 列表渲染:wx:for 与余票状态展示

拿到数据后,result.wxml用wx:for渲染车次列表。每个车次显示车次号、出发到达时间、历时和余票状态。

<view wx:if="{{loading}}">加载中...</view> <view wx:else> <view wx:for="{{trains}}" wx:key="trainNo" class="train-item"> <text>{{item.trainNo}}</text> <text>{{item.fromTime}} - {{item.toTime}}</text> <text>{{item.duration}}</text> <text class="{{item.seatCount > 0 ? 'has-seat' : 'no-seat'}}"> {{item.seatCount > 0 ? '有票' : '无票'}} </text> </view> </view>

wx:key用trainNo保证列表更新时不会错位。余票状态用三元表达式切换 class,has-seat和no-seat在wxss里定义颜色。如果接口返回的余票是数字,直接比较;如果是字符串「有」「无」,需要改判断条件。这里我一般会加一个item.seatCount的默认值,防止字段缺失导致渲染空白。

3. 核心模块拆解:查询参数处理与数据映射

3.1 车站数据:本地字典与拼音匹配

火车票查询有个绕不开的问题:用户输入「北京」还是「北京西」?项目里用了一个本地车站字典,放在utils/stations.js,把常用车站名和拼音缩写都列出来。

// utils/stations.js const stations = [ { name: '北京', code: 'BJP', pinyin: 'beijing' }, { name: '北京西', code: 'BXP', pinyin: 'beijingxi' }, { name: '上海', code: 'SHH', pinyin: 'shanghai' }, { name: '广州', code: 'GZQ', pinyin: 'guangzhou' } ]; module.exports = stations;

查询前先用stations.find把用户输入转成车站代码,再传给接口。如果找不到匹配项,就提示「车站不存在」。这个字典可以按需扩充,但注意代码要和接口要求的一致,不同数据源可能用不同的三字码。我一般会把字典单独维护,不混在页面逻辑里,方便后续替换成远程接口。

3.2 日期格式化:补零与接口兼容

日期选择器返回的格式是YYYY-MM-DD,但有些接口要求YYYYMMDD。项目里在utils/util.js写了一个转换函数。

// utils/util.js function formatDate(dateStr) { return dateStr.replace(/-/g, ''); } module.exports = { formatDate };

调用时formatDate('2025-01-01')返回20250101。如果接口要求带时间戳,可以再包一层new Date(dateStr).getTime()。注意时区问题,小程序运行在用户设备上,new Date会按本地时区解析,跨时区场景要统一用 UTC。

3.3 请求参数组装:从页面到接口的完整链路

把前面的步骤串起来,result.js里实际组装参数的逻辑是这样的:

const stations = require('../../utils/stations.js'); const { formatDate } = require('../../utils/util.js'); queryTrains(from, to, date) { const fromStation = stations.find(s => s.name === from || s.pinyin === from); const toStation = stations.find(s => s.name === to || s.pinyin === to); if (!fromStation || !toStation) { wx.showToast({ title: '车站名有误', icon: 'none' }); return; } wx.request({ url: config.QUERY_TRAINS, data: { from: fromStation.code, to: toStation.code, date: formatDate(date) }, // ... 成功失败回调同上 }); }

这里先做车站匹配,支持中文名和拼音两种输入。find返回第一个匹配项,如果有重名车站(比如「北京」和「北京西」都包含「北京」),需要更精确的匹配规则,比如优先完全相等。日期转换放在请求前,保证接口收到的格式统一。如果接口返回的车次字段和页面渲染字段不一致,可以在success里做一层映射,把train_code转成trainNo之类的。

4. 避坑与排查:调试查询功能时容易翻车的几个点

4.1 现象:开发者工具里请求成功,真机预览失败

原因:小程序正式环境要求请求域名在后台配置白名单,且必须是 HTTPS。开发者工具默认不校验,真机走严格模式。

解决:在微信公众平台后台「开发管理」里添加 request 合法域名。如果只是本地调试,可以在开发者工具「详情」里勾选「不校验合法域名、web-view(业务域名)、TLS 版本以及 HTTPS 证书」。但上线前必须配好域名,否则用户端会直接报错。

4.2 现象:列表渲染出来但余票状态全是「无票」

原因:接口返回的余票字段可能是字符串"0"或"有",而代码里用> 0比较,字符串和数字比较会触发隐式转换,但"有" > 0是false。

解决:先打印res.data.list看实际字段类型。如果是字符串,用item.seatCount !== '0'或parseInt(item.seatCount) > 0。我一般会在setData之前做一次数据清洗,把余票统一转成数字。

4.3 现象:跳转结果页后参数丢失或乱码

原因:URL 拼接时没有对中文或特殊字符做编码,wx.navigateTo的url长度也有限制。

解决:用encodeURIComponent包一层,接收时用decodeURIComponent还原。如果参数很多,可以只传一个 ID,在结果页重新请求详情。另外注意url最大长度是 1024 字节,超长会跳转失败。

4.4 现象:查询接口返回 403 或频繁超时

原因:部分公开接口有频率限制或需要特定请求头,比如Referer或User-Agent。

解决:小程序wx.request不能自定义User-Agent,但可以加header字段。如果接口对来源校验严格,建议换一个稳定的数据源,或者自己搭一层后端转发。不要在小程序里硬编码密钥,容易被反编译。

4.5 现象:日期选择器在 iOS 上显示英文

原因:picker组件的语言跟随系统,iOS 某些版本会显示英文月份。

解决:这是系统级行为,不影响功能。如果必须统一中文,可以用自定义 picker 或第三方组件库。我一般接受这个差异,因为用户对日期格式的敏感度不高。

5. 二次开发与改造:从查询工具到可复用的数据面板

这个项目最值钱的地方不是查询本身,而是它把「输入 → 请求 → 渲染」这条链路跑通了。如果你想把它改成别的查询工具,比如航班、汽车票、快递,只需要换三样东西:车站字典、接口地址、列表字段映射。我一般会先把utils/config.js里的BASE_URL抽成环境变量,用wx.getAccountInfoSync判断开发版还是正式版,分别指向测试和线上接口。

// utils/config.js 改造版 const env = wx.getAccountInfoSync().miniProgram.envVersion; const BASE_URL = env === 'develop' ? 'https://test-api.example.com' : 'https://api.example.com'; module.exports = { QUERY_TRAINS: `${BASE_URL}/train/query` };

这样切环境不用改代码,提交审核前也不会误连测试数据。另一个改造点是加缓存。查询结果可以按from+to+date存到wx.setStorageSync,下次相同条件先读缓存,减少请求次数。注意缓存要设过期时间,火车票余票变化快,建议 5 分钟失效。

const cacheKey = `train_${from}_${to}_${date}`; const cached = wx.getStorageSync(cacheKey); if (cached && Date.now() - cached.time < 5 * 60 * 1000) { this.setData({ trains: cached.list, loading: false }); return; } // 请求成功后写入缓存 wx.setStorageSync(cacheKey, { list: res.data.list, time: Date.now() });

如果你想让列表支持下拉刷新,在result.json里加"enablePullDownRefresh": true,然后在onPullDownRefresh里重新调用queryTrains,完成后wx.stopPullDownRefresh()。这个改动很小,但体验提升明显。

最后说一个我踩过的坑:不要直接把接口返回的整个对象塞进setData,数据量大的时候会触发性能警告。只取渲染需要的字段,比如trainNo、fromTime、toTime、seatCount,其他字段留在内存里备用。从那以后我每次做列表渲染都强制走一遍字段裁剪,希望帮到你。

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

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

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

立即咨询