微信小程序火车票查询源码解析:从请求封装到列表渲染的完整实践

简介:这是一份面向微信小程序初学者与前端开发者的实战型源码资源,围绕火车票查询场景,演示如何在小程序中调用接口完成车次检索与结果展示,适合作为课程设计、练手项目或二次开发的基础模板。压缩包共23个文件,约11KB,以js逻辑脚本、json页面配置、wxss样式表和wxml结构文件为主,另附一份说明文档,整体结构轻量,便于快速阅读与改造。资源按页面与工具模块划分,包含首页、车次列表、座位信息、日志等页面目录,以及请求封装与通用工具函数,读者可从中了解小程序页面注册、数据请求与渲染的完整链路。目前已有1643人学习下载,可作为理解小程序接口调用与页面组织方式的参考,并在此基础上扩展车站选择、日期筛选、订单流程等功能。

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

每年临近假期,后台总会收到类似的问题:有没有一个能直接塞进微信里、打开就能查车次的小工具?这份「微信小程序-火车票查询」源码包,解决的正是这个场景。它把车次查询、出发到达站选择、日期切换、余票与票价展示这几件事,压缩进一个不需要安装、扫码即用的小程序里。适合两类人:一类是想快速跑通一个完整小程序项目的新手,另一类是打算把它当成骨架,接入自己后端接口做二次开发的熟手。整个项目围绕「查询」这一条主线展开,页面不多,但请求封装、数据渲染、表单交互这些小程序开发的核心环节一个不少。下面我按「能干什么 → 怎么跑 → 怎么改」的顺序,把它拆开讲清楚。

2. 项目结构与技术选型:一个查询类小程序的骨架长什么样

拿到源码包,第一件事不是急着点编译,而是先看清目录。小程序项目的结构相对固定,但不同作者的组织习惯差别很大,先摸清骨架,后面改起来才不至于翻车。

2.1 目录结构逐层拆解

一个典型的火车票查询小程序,目录大致是这样组织的:

train-ticket-miniprogram/ ├── app.js // 小程序入口,全局逻辑 ├── app.json // 全局配置:页面路径、窗口样式 ├── app.wxss // 全局样式 ├── project.config.json // 开发者工具项目配置 ├── sitemap.json // 页面收录配置 ├── pages/ │ ├── index/ // 首页:查询条件输入 │ │ ├── index.js │ │ ├── index.wxml │ │ ├── index.wxss │ │ └── index.json │ ├── result/ // 结果页:车次列表展示 │ │ ├── result.js │ │ ├── result.wxml │ │ ├── result.wxss │ │ └── result.json │ └── detail/ // 详情页:单个车次信息 │ ├── detail.js │ ├── detail.wxml │ ├── detail.wxss │ └── detail.json ├── utils/ │ ├── request.js // 网络请求封装 │ └── format.js // 日期、时间格式化 └── images/ // 图标与静态资源

app.json里的pages数组决定了页面注册顺序,数组第一项就是启动页。查询类小程序一般把pages/index/index放在首位,用户打开直接看到查询表单。utils/request.js是整个项目的数据入口,所有跟车次相关的请求都从这里走,改接口地址、加请求头、处理错误码,基本都在这一个文件里动手。

2.2 技术选型:为什么是原生小程序而不是框架

这份源码用的是微信原生小程序语法,没有引入 uni-app、Taro 这类跨端框架。原因很直接:查询类小程序页面少、交互简单,原生写法体积小、启动快,调试时也不用多一层编译。数据请求用wx.request,状态管理靠页面data加setData,没有 Redux 或 MobX 那套东西。

常见做法是在app.js里挂一个全局的globalData,用来存用户当前选中的出发站、到达站和日期,这样从首页跳到结果页时不用重复传参。我一般会把这类跨页共享的数据统一放globalData,页面内只保留跟渲染强相关的状态,避免setData传一大堆用不上的字段。

// app.js App({ globalData: { fromStation: '', // 出发站 toStation: '', // 到达站 trainDate: '', // 乘车日期 baseUrl: 'https://example.com/api' // 接口基地址,改这里换后端 }, onLaunch() { // 启动时初始化日期为今天 const today = new Date(); this.globalData.trainDate = this.formatDate(today); }, formatDate(date) { const y = date.getFullYear(); const m = String(date.getMonth() + 1).padStart(2, '0'); const d = String(date.getDate()).padStart(2, '0'); return `${y}-${m}-${d}`; } });

这段入口逻辑做了两件事:一是把跨页共享的查询条件集中管理,二是启动时把日期默认成今天。baseUrl单独抽出来,是为了后面换后端时只改一处。参数说明上,fromStation和toStation存的是站点名称或站点代码,具体用哪种取决于后端接口约定,改的时候要跟接口对齐。

2.3 页面注册与窗口配置

app.json除了注册页面,还负责窗口标题、导航栏颜色这些外观配置。查询类小程序通常把导航栏标题设成「火车票查询」,背景色用默认白或浅灰,保持信息清晰。

{ "pages": [ "pages/index/index", "pages/result/result", "pages/detail/detail" ], "window": { "navigationBarTitleText": "火车票查询", "navigationBarBackgroundColor": "#ffffff", "navigationBarTextStyle": "black", "backgroundColor": "#f5f5f5" }, "sitemapLocation": "sitemap.json" }

pages数组的顺序就是页面栈的注册顺序,新增页面必须在这里补一行,否则跳转时会报「页面不存在」。window里的配置是全局默认值,单个页面可以在自己的.json里覆盖,比如结果页想把标题改成「查询结果」,就在pages/result/result.json里单独写navigationBarTitleText。

3. 核心查询链路:从输入车站到渲染车次列表

骨架清楚之后,重点看数据是怎么流动的。查询类小程序的核心链路就一条:用户在首页填条件 → 跳转结果页 → 结果页发请求 → 拿到数据渲染列表。这条链路里,请求封装和列表渲染是最容易出问题的地方。

3.1 请求封装:把 wx.request 包一层

原生wx.request是回调式的,直接在页面里写会散落一堆重复代码。项目里utils/request.js做的就是把它包成 Promise,统一处理基地址、超时和错误提示。

// utils/request.js const app = getApp(); function request(options) { return new Promise((resolve, reject) => { wx.request({ url: app.globalData.baseUrl + options.url, // 拼接基地址 method: options.method || 'GET', data: options.data || {}, header: { 'content-type': 'application/json', ...options.header }, timeout: 10000, // 10 秒超时 success(res) { if (res.statusCode === 200 && res.data.code === 0) { resolve(res.data.data); // 只把业务数据抛出去 } else { wx.showToast({ title: res.data.msg || '请求失败', icon: 'none' }); reject(res.data); } }, fail(err) { wx.showToast({ title: '网络异常', icon: 'none' }); reject(err); } }); }); } module.exports = { request };

这段封装的关键点有三个:baseUrl从全局配置取,换环境不用改这里;timeout设成 10 秒,避免弱网下一直转圈;成功回调里判断statusCode和业务code,只有都通过才resolve,否则统一弹提示并reject。参数上,options.url是相对路径,options.data是查询参数,options.header允许单个请求追加头部。改后端时,只要新接口的返回结构是{ code, msg, data },这套封装基本不用动;如果返回结构不同,改success里的判断即可。

3.2 首页表单:车站选择与日期切换

首页负责收集查询条件。出发站和到达站一般用picker或自定义弹层选择,日期用picker mode="date"。核心是把用户的选择同步到data,跳转前再写进globalData。

// pages/index/index.js const app = getApp(); Page({ data: { fromStation: '', toStation: '', trainDate: '', stationList: ['北京南', '上海虹桥', '广州南', '深圳北'] // 示例站点 }, onLoad() { // 回填全局数据,从结果页返回时保留上次选择 this.setData({ fromStation: app.globalData.fromStation, toStation: app.globalData.toStation, trainDate: app.globalData.trainDate }); }, onFromChange(e) { this.setData({ fromStation: this.data.stationList[e.detail.value] }); }, onToChange(e) { this.setData({ toStation: this.data.stationList[e.detail.value] }); }, onDateChange(e) { this.setData({ trainDate: e.detail.value }); }, onQuery() { const { fromStation, toStation, trainDate } = this.data; if (!fromStation || !toStation) { wx.showToast({ title: '请选择出发和到达站', icon: 'none' }); return; } if (fromStation === toStation) { wx.showToast({ title: '出发到达站不能相同', icon: 'none' }); return; } // 写入全局,供结果页读取 app.globalData.fromStation = fromStation; app.globalData.toStation = toStation; app.globalData.trainDate = trainDate; wx.navigateTo({ url: '/pages/result/result' }); } });

onLoad里回填globalData是个实用细节:用户从结果页返回首页时,上次选的条件还在,不用重新选一遍。onQuery里做了两层校验,空值和同站都拦下来,避免发无效请求。跳转用navigateTo而不是redirectTo,是为了保留页面栈,用户能返回修改条件。参数上,e.detail.value是picker返回的索引,用它去stationList里取值;如果站点数据来自接口,这里要改成异步加载后再渲染。

3.3 结果页:发请求与列表渲染

结果页在onLoad里读取globalData,拼参数发请求,拿到车次数组后setData渲染。列表用wx:for循环,每个车次展示车次号、出发到达时间、历时和票价。

// pages/result/result.js const app = getApp(); const { request } = require('../../utils/request'); Page({ data: { trainList: [], loading: true }, onLoad() { this.queryTrains(); }, queryTrains() { const { fromStation, toStation, trainDate } = app.globalData; this.setData({ loading: true }); request({ url: '/train/query', data: { from: fromStation, to: toStation, date: trainDate } }).then(list => { this.setData({ trainList: list, loading: false }); }).catch(() => { this.setData({ loading: false }); }); }, onTrainTap(e) { // 点击某个车次,带车次号跳详情 const trainNo = e.currentTarget.dataset.trainno; wx.navigateTo({ url: `/pages/detail/detail?trainNo=${trainNo}` }); } });

queryTrains把全局里的三个条件拼成请求参数,request返回 Promise,then里直接拿到业务数据数组。loading状态用来控制加载提示,请求结束无论成功失败都置回false,避免一直转圈。onTrainTap通过><!-- pages/result/result.wxml --> <view class="result-page"> <view wx:if="{{loading}}" class="loading">查询中...</view> <view wx:elif="{{trainList.length === 0}}" class="empty">暂无车次</view> <view wx:else class="train-list"> <view class="train-item" wx:for="{{trainList}}" wx:key="trainNo" >// 改造后的 request.js 片段:适配不同返回结构 success(res) { if (res.statusCode !== 200) { wx.showToast({ title: '服务异常', icon: 'none' }); reject(res); return; } // 假设新后端直接返回数组,没有 code 包裹 const data = Array.isArray(res.data) ? res.data : res.data.data; resolve(data); }

这段改造把「业务码判断」去掉,直接按数组或data字段取值。改的时候要注意:一旦去掉统一错误码,页面里的catch就要承担更多错误提示职责,别让失败静默。

5.2 加功能:从「查询」到「订阅提醒」

查询类小程序最容易加的功能是「车次收藏」和「余票提醒」。收藏用wx.setStorageSync存本地即可,不需要后端。余票提醒则需要一个定时任务或订阅消息,小程序侧用wx.requestSubscribeMessage申请订阅,后端定时查余票并推送。

功能实现方式改动文件
车次收藏本地 Storage 存车次号数组新增utils/storage.js,结果页加收藏按钮
余票提醒订阅消息 + 后端定时任务详情页加订阅按钮,后端加推送逻辑
历史查询本地存最近查询条件首页onQuery里写入,onShow里读取

表格里三条改造,收藏和历史查询纯前端就能完成,适合先练手;余票提醒涉及后端和消息推送,改动面大,建议先把查询链路跑稳再动。

5.3 验证改动:别只看页面,看数据流

改完之后怎么确认没改坏?我的习惯是分三层验证:第一层,在request.js里打日志,确认请求参数和返回数据符合预期;第二层,在结果页setData前后打日志,确认渲染数据正确;第三层,真机预览,重点测弱网和空数据两种情况。开发者工具的「Network」面板能看到请求详情,但真机上的表现才是最终标准。

5.4 一个具体技巧:用 mock 数据先跑通 UI

换后端时,接口往往还没准备好。这时候别干等,先在request.js里加一个开关,返回本地 mock 数据,把 UI 和交互先跑通。

// utils/request.js 里加 mock 开关 const USE_MOCK = true; // 改 false 走真实接口 function request(options) { if (USE_MOCK) { return new Promise(resolve => { // 模拟 500ms 延迟,贴近真实请求 setTimeout(() => { resolve([ { trainNo: 'G101', startTime: '08:00', arriveTime: '12:30', duration: '4h30m', price: 553 }, { trainNo: 'G103', startTime: '09:15', arriveTime: '13:50', duration: '4h35m', price: 553 } ]); }, 500); }); } // ... 真实请求逻辑 }

这个开关的好处是前后端解耦:UI 调好了,接口一接就能用。USE_MOCK上线前记得改回false,我吃过一次亏,测试环境忘了关,结果线上一直显示假数据,排查了半天。从那以后我每次提交前都强制搜一遍USE_MOCK,确认状态再推。希望帮到你。

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

相关新闻

AI大战PowerShell:脚本跑不通的四大根源与解决模板

AI大战PowerShell:脚本跑不通的四大根源与解决模板

你有没有过这样的经历&#xff1a;手头一堆重复性的系统任务&#xff0c;实在懒得逐条敲命令&#xff0c;随手把需求丢给 AI 助手&#xff0c;AI 也很爽快地吐出一段 PowerShell 脚本。你复制、粘贴、回车&#xff0c;然后看着满屏红色报错发呆。我最近连续处理了好几个这种场景…

2026/10/20 5:06:58 阅读更多 →
LangChain 1.0 Community生态全解析:第三方集成与迁移实践指南

LangChain 1.0 Community生态全解析:第三方集成与迁移实践指南

写这个东西之前我想先交代一个背景&#xff1a;如果你是从 LangChain 0.x 一路用上来的老用户&#xff0c;应该都经历过那种"不装不对、装多也错"的依赖地狱。新项目还没跑起来&#xff0c;先花一晚上解决包冲突&#xff1b;好不容易跑通了&#xff0c;换一个模型厂商…

2026/10/20 5:06:58 阅读更多 →
智能车变道全链路拆解:从感知到控制的算法与工程实践

智能车变道全链路拆解:从感知到控制的算法与工程实践

前方两公里就是出口&#xff0c;右侧车道一台速度明显偏慢的货车正好堵住去路&#xff0c;后视镜里还有一辆试图从右侧挤上来的小车。老司机大概扫一眼&#xff0c;抬脚、打灯、小幅度加速&#xff0c;变道一气呵成。但把这个动作交给一台智能车时&#xff0c;问题就复杂了&…

2026/10/20 5:06:58 阅读更多 →

最新新闻

Keras大文件训练内存溢出解决方案:三层数据流架构

Keras大文件训练内存溢出解决方案:三层数据流架构

1. 为什么“大文件无法一次性读入内存”是Keras用户绕不开的坎&#xff1f;Keras作为高层深度学习API&#xff0c;最大的优势是简洁、直观、上手快——写个model.fit(X_train, y_train)就能跑通一个模型。但这个看似简单的接口背后&#xff0c;藏着一个被新手反复踩坑、被老手默…

2026/10/20 5:39:13 阅读更多 →
为什么代码写得对却不值钱?工程师价值重估指南

为什么代码写得对却不值钱?工程师价值重估指南

1. 这不是代码批判&#xff0c;而是工程师价值重估的现场“你的代码不值钱&#xff0c;你为什么这么写才值钱”——这句话刚在技术社区刷屏时&#xff0c;我正帮某高校实验室重构一套运行了七年的教学评估系统。那套代码语法完全合规&#xff0c;单元测试覆盖率92%&#xff0c;…

2026/10/20 5:39:13 阅读更多 →
Playwright驱动浏览器自动化:破解无限瀑布流动态加载的爬虫实战

Playwright驱动浏览器自动化:破解无限瀑布流动态加载的爬虫实战

直接开始。玩爬虫的人多少都遇到过这种场景&#xff1a;目标网站数据明明就摆在页面上&#xff0c;但用 requests 怎么抓都只能拿到首页那一小段&#xff0c;往下翻页面&#xff0c;心跳都跟着滚轮一起加速&#xff0c;可手里的代码就是纹丝不动。小红书这种重 JS 交互的平台&a…

2026/10/20 5:39:13 阅读更多 →
32位系统内存补丁实战:PatchPae2让老电脑用满4G以上内存

32位系统内存补丁实战:PatchPae2让老电脑用满4G以上内存

简介&#xff1a;PatchPae2补丁面向32位的Windows 8.1操作系统&#xff0c;通过启用系统物理地址扩展功能&#xff0c;突破4GB内存识别上限&#xff0c;解决系统只能使用部分物理内存的痛点&#xff0c;适合受内存容量制约的用户、开发者和运维人员&#xff1b;补丁以修改内核方…

2026/10/20 5:39:13 阅读更多 →
MCP工具调用机制与优化实践:从原理到工程落地

MCP工具调用机制与优化实践:从原理到工程落地

1. MCP 工具调用的核心机制拆解1.1 从“对话”到“行动”&#xff1a;MCP 到底解决了什么问题MCP&#xff08;Model Context Protocol&#xff09;现在几乎是 AI 应用开发绕不开的协议了&#xff0c;但很多刚接触的人容易把注意力放在“这是什么”上面&#xff0c;忽略了一个更…

2026/10/20 5:39:13 阅读更多 →
原生JavaScript实现贪吃蛇:核心逻辑与Canvas渲染实战

原生JavaScript实现贪吃蛇:核心逻辑与Canvas渲染实战

最近整理硬盘上的旧文件夹&#xff0c;翻出一个好几年前写的JavaScript贪吃蛇。当时是某段时间练手用的&#xff0c;代码写得很粗糙&#xff0c;注释也不全&#xff0c;但核心逻辑基本完整&#xff0c;跑起来没有问题。这几天花时间把代码理清爽、补上注释、顺手修掉几个当年没…

2026/10/20 5:38:13 阅读更多 →

日新闻

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →
ESP32舵机控制指南:原理、多路驱动与避坑实战

ESP32舵机控制指南:原理、多路驱动与避坑实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →
位移法实现一阶低通滤波:单片机ADC采样降噪实战

位移法实现一阶低通滤波:单片机ADC采样降噪实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型&#xff08;GMM&#xff09;的语音识别系统&#xff0c;很多人第一反应就是一堆数学公式&#xff0c;觉得门槛很高。但如果你真的在MATLAB里把它落地跑通&#xff0c;会发现核心其实就是三件事&#xff1a;把声音变成特征向量&#xff0c;用GMM给每个类别…

2026/10/19 10:02:04 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人&#xff0c;但落到具体操作上&#xff0c;无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程&#xff1a;对图像做反白、改调色板、彩色转灰度&#xff0c;最后把像素数据导出成txt文件。单看每一…

2026/10/19 17:32:41 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词&#xff0c;你会发现一个很有意思的现象&#xff1a;每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型&#xff0c;有人指的是运动控制系统&#xff0c;还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/20 2:50:50 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 2:54:50 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 9:10:22 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 12:16:16 阅读更多 →