戰(zhàn):從請(qǐng)求封裝到列表渲染)
簡(jiǎn)介這份資源是一套基于微信小程序?qū)崿F(xiàn)的火車票查詢系統(tǒng)源碼面向具備小程序基礎(chǔ)語法、希望練習(xí)接口調(diào)用與頁(yè)面聯(lián)調(diào)的開發(fā)者也可作為課程設(shè)計(jì)或畢業(yè)設(shè)計(jì)的起步模板。項(xiàng)目圍繞車次查詢、站點(diǎn)信息與余票展示等典型場(chǎng)景組織代碼方便在此基礎(chǔ)上繼續(xù)擴(kuò)展訂單、登錄等功能。壓縮包共23個(gè)文件約11KB包含8個(gè)js邏輯腳本、5個(gè)json配置、5個(gè)wxss樣式、4個(gè)wxml頁(yè)面結(jié)構(gòu)以及1個(gè)md說明文檔分別承擔(dān)業(yè)務(wù)邏輯、頁(yè)面配置、界面樣式與視圖渲染等職責(zé)目錄按頁(yè)面與工具模塊劃分結(jié)構(gòu)清晰。目前已有1643人學(xué)習(xí)下載。讀者可從中獲取完整的小程序請(qǐng)求封裝與頁(yè)面交互示例理解api與request工具模塊的調(diào)用方式并參考其目錄組織與配置寫法快速搭建自己的查詢類小程序減少?gòu)牧忝鞯某杀尽?. 從一次搶票失敗說起這個(gè)火車票查詢小程序到底能干什么去年春運(yùn)前我?guī)团笥芽匆粋€(gè)搶票腳本的問題結(jié)果發(fā)現(xiàn)他連車次余票都查不明白——用的還是某個(gè)第三方網(wǎng)頁(yè)接口返回的數(shù)據(jù)結(jié)構(gòu)一變整個(gè)腳本就崩了。當(dāng)時(shí)我就想與其依賴別人維護(hù)的接口不如自己寫一個(gè)能跑通查詢鏈路的小程序。后來在代碼倉(cāng)庫(kù)里翻到了這個(gè)「微信小程序-火車票查詢」項(xiàng)目它正好解決了一個(gè)很具體的需求在微信里輸入出發(fā)站、到達(dá)站和日期直接拿到車次列表和余票信息不用跳來跳去。這個(gè)項(xiàng)目適合兩類人一是想學(xué)小程序網(wǎng)絡(luò)請(qǐng)求和列表渲染的新手代碼結(jié)構(gòu)簡(jiǎn)單跑起來就能看到數(shù)據(jù)二是需要快速搭一個(gè)查詢工具原型的開發(fā)者核心邏輯抽出來就能復(fù)用。它不涉及支付和下單只做查詢展示所以調(diào)試成本很低不用申請(qǐng)復(fù)雜的商戶資質(zhì)。接下來我按「能跑、能看懂、能改」的順序把這個(gè)項(xiàng)目的骨架和關(guān)鍵實(shí)現(xiàn)拆一遍。2. 項(xiàng)目骨架與運(yùn)行鏈路從目錄結(jié)構(gòu)到第一次請(qǐng)求成功2.1 目錄結(jié)構(gòu)小程序標(biāo)準(zhǔn)分層與頁(yè)面組織拿到源碼后先看根目錄。這個(gè)項(xiàng)目用的是微信小程序原生框架沒有引入第三方 UI 庫(kù)所以目錄很干凈。常見結(jié)構(gòu)是pages放頁(yè)面、utils放工具函數(shù)、app.json做全局配置。我打開時(shí)第一眼找的是app.json里的pages數(shù)組它決定了啟動(dòng)頁(yè)和頁(yè)面路徑。{ pages: [ pages/index/index, pages/result/result ], window: { navigationBarTitleText: 火車票查詢, navigationBarBackgroundColor: #1E90FF } }這段配置里pages數(shù)組第一項(xiàng)是啟動(dòng)頁(yè)所以打開小程序先看到查詢表單。window里設(shè)了導(dǎo)航欄標(biāo)題和背景色改顏色直接改十六進(jìn)制值就行。注意pages路徑不要寫.js后綴否則編譯會(huì)報(bào)錯(cuò)這是新手常踩的坑。pages/index負(fù)責(zé)輸入pages/result負(fù)責(zé)展示車次列表。兩個(gè)頁(yè)面之間用wx.navigateTo跳轉(zhuǎn)參數(shù)通過 URL 拼接傳遞。這種拆分方式讓查詢邏輯和渲染邏輯分開改起來不會(huì)互相干擾。2.2 查詢表單輸入校驗(yàn)與日期選擇首頁(yè)的index.wxml里放了兩個(gè)輸入框和一個(gè)日期選擇器。出發(fā)站和到達(dá)站用input日期用picker modedate。我一般會(huì)在這里加一個(gè)非空校驗(yàn)防止用戶直接點(diǎn)查詢導(dǎo)致請(qǐng)求參數(shù)為空。// 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: 請(qǐng)?zhí)顚懲暾? icon: none }); return; } wx.navigateTo({ url: /pages/result/result?from${from}to${to}date${date} }); } });setData是小程序更新視圖的核心方法這里把輸入值同步到data。onQuery里先做空值判斷再用模板字符串拼 URL。注意from和to如果包含特殊字符需要encodeURIComponent處理否則跳轉(zhuǎn)后參數(shù)會(huì)截?cái)唷H掌谶x擇器返回的是YYYY-MM-DD格式正好符合大多數(shù)查詢接口的要求。2.3 網(wǎng)絡(luò)請(qǐng)求封裝wx.request 與公共配置查詢請(qǐng)求放在result.js的onLoad里。項(xiàng)目里把請(qǐng)求域名和路徑抽到了utils/config.js這樣換接口時(shí)不用翻遍所有頁(yè)面。// utils/config.js const BASE_URL https://api.example.com; // 替換成實(shí)際可用的查詢接口 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: 網(wǎng)絡(luò)異常, icon: none }); } }); } });wx.request的data字段會(huì)自動(dòng)拼成查詢字符串不用手動(dòng)拼接。success里先判斷業(yè)務(wù)狀態(tài)碼再取list賦值給trains。這里有個(gè)細(xì)節(jié)如果接口返回的字段名不是list需要同步改setData的鍵名。fail回調(diào)處理網(wǎng)絡(luò)層錯(cuò)誤比如域名未配置或超時(shí)。注意小程序正式環(huán)境要求 HTTPS 域名開發(fā)時(shí)可以在開發(fā)者工具里勾選「不校驗(yàn)合法域名」臨時(shí)繞過。2.4 列表渲染wx:for 與余票狀態(tài)展示拿到數(shù)據(jù)后result.wxml用wx:for渲染車次列表。每個(gè)車次顯示車次號(hào)、出發(fā)到達(dá)時(shí)間、歷時(shí)和余票狀態(tài)。view wx:if{{loading}}加載中.../view view wx:else view wx:for{{trains}} wx:keytrainNo classtrain-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 /viewwx:key用trainNo保證列表更新時(shí)不會(huì)錯(cuò)位。余票狀態(tài)用三元表達(dá)式切換 classhas-seat和no-seat在wxss里定義顏色。如果接口返回的余票是數(shù)字直接比較如果是字符串「有」「無」需要改判斷條件。這里我一般會(huì)加一個(gè)item.seatCount的默認(rèn)值防止字段缺失導(dǎo)致渲染空白。3. 核心模塊拆解查詢參數(shù)處理與數(shù)據(jù)映射3.1 車站數(shù)據(jù)本地字典與拼音匹配火車票查詢有個(gè)繞不開的問題用戶輸入「北京」還是「北京西」項(xiàng)目里用了一個(gè)本地車站字典放在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把用戶輸入轉(zhuǎn)成車站代碼再傳給接口。如果找不到匹配項(xiàng)就提示「車站不存在」。這個(gè)字典可以按需擴(kuò)充但注意代碼要和接口要求的一致不同數(shù)據(jù)源可能用不同的三字碼。我一般會(huì)把字典單獨(dú)維護(hù)不混在頁(yè)面邏輯里方便后續(xù)替換成遠(yuǎn)程接口。3.2 日期格式化補(bǔ)零與接口兼容日期選擇器返回的格式是YYYY-MM-DD但有些接口要求YYYYMMDD。項(xiàng)目里在utils/util.js寫了一個(gè)轉(zhuǎn)換函數(shù)。// utils/util.js function formatDate(dateStr) { return dateStr.replace(/-/g, ); } module.exports { formatDate };調(diào)用時(shí)formatDate(2025-01-01)返回20250101。如果接口要求帶時(shí)間戳可以再包一層new Date(dateStr).getTime()。注意時(shí)區(qū)問題小程序運(yùn)行在用戶設(shè)備上new Date會(huì)按本地時(shí)區(qū)解析跨時(shí)區(qū)場(chǎng)景要統(tǒng)一用 UTC。3.3 請(qǐng)求參數(shù)組裝從頁(yè)面到接口的完整鏈路把前面的步驟串起來result.js里實(shí)際組裝參數(shù)的邏輯是這樣的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) }, // ... 成功失敗回調(diào)同上 }); }這里先做車站匹配支持中文名和拼音兩種輸入。find返回第一個(gè)匹配項(xiàng)如果有重名車站比如「北京」和「北京西」都包含「北京」需要更精確的匹配規(guī)則比如優(yōu)先完全相等。日期轉(zhuǎn)換放在請(qǐng)求前保證接口收到的格式統(tǒng)一。如果接口返回的車次字段和頁(yè)面渲染字段不一致可以在success里做一層映射把train_code轉(zhuǎn)成trainNo之類的。4. 避坑與排查調(diào)試查詢功能時(shí)容易翻車的幾個(gè)點(diǎn)4.1 現(xiàn)象開發(fā)者工具里請(qǐng)求成功真機(jī)預(yù)覽失敗原因小程序正式環(huán)境要求請(qǐng)求域名在后臺(tái)配置白名單且必須是 HTTPS。開發(fā)者工具默認(rèn)不校驗(yàn)真機(jī)走嚴(yán)格模式。解決在微信公眾平臺(tái)后臺(tái)「開發(fā)管理」里添加 request 合法域名。如果只是本地調(diào)試可以在開發(fā)者工具「詳情」里勾選「不校驗(yàn)合法域名、web-view業(yè)務(wù)域名、TLS 版本以及 HTTPS 證書」。但上線前必須配好域名否則用戶端會(huì)直接報(bào)錯(cuò)。4.2 現(xiàn)象列表渲染出來但余票狀態(tài)全是「無票」原因接口返回的余票字段可能是字符串0或有而代碼里用 0比較字符串和數(shù)字比較會(huì)觸發(fā)隱式轉(zhuǎn)換但有 0是false。解決先打印res.data.list看實(shí)際字段類型。如果是字符串用item.seatCount ! 0或parseInt(item.seatCount) 0。我一般會(huì)在setData之前做一次數(shù)據(jù)清洗把余票統(tǒng)一轉(zhuǎn)成數(shù)字。4.3 現(xiàn)象跳轉(zhuǎn)結(jié)果頁(yè)后參數(shù)丟失或亂碼原因URL 拼接時(shí)沒有對(duì)中文或特殊字符做編碼wx.navigateTo的url長(zhǎng)度也有限制。解決用encodeURIComponent包一層接收時(shí)用decodeURIComponent還原。如果參數(shù)很多可以只傳一個(gè) ID在結(jié)果頁(yè)重新請(qǐng)求詳情。另外注意url最大長(zhǎng)度是 1024 字節(jié)超長(zhǎng)會(huì)跳轉(zhuǎn)失敗。4.4 現(xiàn)象查詢接口返回 403 或頻繁超時(shí)原因部分公開接口有頻率限制或需要特定請(qǐng)求頭比如Referer或User-Agent。解決小程序wx.request不能自定義User-Agent但可以加header字段。如果接口對(duì)來源校驗(yàn)嚴(yán)格建議換一個(gè)穩(wěn)定的數(shù)據(jù)源或者自己搭一層后端轉(zhuǎn)發(fā)。不要在小程序里硬編碼密鑰容易被反編譯。4.5 現(xiàn)象日期選擇器在 iOS 上顯示英文原因picker組件的語言跟隨系統(tǒng)iOS 某些版本會(huì)顯示英文月份。解決這是系統(tǒng)級(jí)行為不影響功能。如果必須統(tǒng)一中文可以用自定義 picker 或第三方組件庫(kù)。我一般接受這個(gè)差異因?yàn)橛脩魧?duì)日期格式的敏感度不高。5. 二次開發(fā)與改造從查詢工具到可復(fù)用的數(shù)據(jù)面板這個(gè)項(xiàng)目最值錢的地方不是查詢本身而是它把「輸入 → 請(qǐng)求 → 渲染」這條鏈路跑通了。如果你想把它改成別的查詢工具比如航班、汽車票、快遞只需要換三樣?xùn)|西車站字典、接口地址、列表字段映射。我一般會(huì)先把utils/config.js里的BASE_URL抽成環(huán)境變量用wx.getAccountInfoSync判斷開發(fā)版還是正式版分別指向測(cè)試和線上接口。// 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 };這樣切環(huán)境不用改代碼提交審核前也不會(huì)誤連測(cè)試數(shù)據(jù)。另一個(gè)改造點(diǎn)是加緩存。查詢結(jié)果可以按fromtodate存到wx.setStorageSync下次相同條件先讀緩存減少請(qǐng)求次數(shù)。注意緩存要設(shè)過期時(shí)間火車票余票變化快建議 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; } // 請(qǐng)求成功后寫入緩存 wx.setStorageSync(cacheKey, { list: res.data.list, time: Date.now() });如果你想讓列表支持下拉刷新在result.json里加enablePullDownRefresh: true然后在onPullDownRefresh里重新調(diào)用queryTrains完成后wx.stopPullDownRefresh()。這個(gè)改動(dòng)很小但體驗(yàn)提升明顯。最后說一個(gè)我踩過的坑不要直接把接口返回的整個(gè)對(duì)象塞進(jìn)setData數(shù)據(jù)量大的時(shí)候會(huì)觸發(fā)性能警告。只取渲染需要的字段比如trainNo、fromTime、toTime、seatCount其他字段留在內(nèi)存里備用。從那以后我每次做列表渲染都強(qiáng)制走一遍字段裁剪希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取