简介:这是一份面向微信小程序初学者与前端开发者的实战型源码资源,围绕火车票查询场景,演示如何在小程序中调用接口完成车次检索与结果展示,适合作为课程设计、练手项目或二次开发的基础模板。压缩包共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,确认状态再推。希望帮到你。本文还有配套的精品资源,点击获取
简介:这是一份面向微信小程序初学者与前端开发者的实战型源码资源,围绕火车票查询场景,演示如何在小程序中调用接口完成车次检索与结果展示,适合作为课程设计、练手项目或二次开发的基础模板。压缩包共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,确认状态再推。希望帮到你。本文还有配套的精品资源,点击获取