前言:小程序文件選擇的尷尬
在微信小程序里做「上傳文件」功能時,很多人會第一時間想到 wx.chooseMessageFile。用了一段時間才發現:它只能從聊天記錄里選文件,根本不會調起系統文件管理器。
這就導致:
- 用戶:沒法從 iCloud Drive、本地文件夾、最近下載里選文件,只能先把文件發到某個聊天再選,體驗很割裂
- 產品訴求:希望支持「從手機文件導入」,和「從聊天導入」并存
- 平臺限制:小程序沒有提供「調起系統文件選擇器」的 API,只能另辟蹊徑
所以我們的目標很明確:在不大改現有邏輯的前提下,增加一種「從手機本地文件導入」的方式。最后采用的方案是:用 <web-view> 打開一個 H5 頁面,在 H5 里用 <input type="file"> 調起系統文件選擇器,選完上傳后再通知小程序刷新列表。
下面就是這條路上的踩坑和實現要點,給有類似需求的同學做個參考。
踩坑經歷
坑一:以為 postMessage 能實時通知小程序
H5 里上傳成功后,很自然就想用 wx.miniProgram.postMessage({ success: true }) 告訴小程序「上傳好了,去刷新列表吧」。
結果發現:postMessage 并不是實時送達的。消息只會在 web-view 的特定時機(例如用戶點擊返回、分享、頁面被銷毀)才被小程序收到,不能指望「上傳接口返回 200 就立刻讓列表刷新」。
所以更穩妥的做法是:上傳成功后直接 wx.miniProgram.navigateBack() 回到列表頁,在列表頁的 onShow 里統一做一次刷新。這樣不依賴 postMessage 的時機,邏輯也更簡單。
坑二:業務域名和服務器域名搞混
在微信里,業務域名和服務器域名是兩套配置:
- 業務域名:
<web-view> 的 src 必須是已配置的業務域名下的頁面,否則體驗版/正式版里 web-view 會白屏或報錯 - 服務器域名:request、uploadFile 等接口要請求的 API 域名,在「服務器域名」里配置
我們一開始只在「服務器域名」里配了 API 的域名,結果 web-view 加載的 H5 地址沒進「業務域名」,真機上就打不開。記得把 H5 頁面的域名(例如 https://your-h5.com)配到「開發管理 → 開發設置 → 業務域名」,并且域名根路徑要放好微信的校驗文件。
坑三:H5 頁面和 API 的域名沒分開配
H5 頁面可能部署在前端靜態資源域名,上傳接口在后端 API 域名,兩者不一定同域。如果只在 H5 里寫死一個 baseURL,以后換環境、換域名就要改代碼。
我們的做法是:小程序跳轉 web-view 時,把當前環境的 API 根地址通過 URL 參數(如 api_base)傳給 H5,H5 用這個參數拼上傳接口的完整 URL。這樣小程序側用 getEnvBaseUrl() 之類的方法根據環境變量取 API 地址即可,H5 只認參數,方便多環境部署。
坑四:web-view 里該用獨立 HTML 還是 Vue 頁面
web-view 里跑的是完整瀏覽器環境,理論上可以塞進去一個 Vue 應用。但考慮到:
- 這個頁面只有一個「選文件 → 上傳」的簡單流程
- 希望加載盡量快、不依賴一堆 JS 庫
- 部署要簡單,最好和主站一起發版即可
我們最終選的是:在 public/h5/ 下放一個獨立的 upload-file.html,不經過 Vue 打包,只引微信 JS-SDK,用原生 JS 做選文件和 XHR 上傳。這樣無需額外構建、體積小、首屏快,也避免和主項目路由、構建環境耦合。
坑五:token 怎么安全地給到 H5
H5 調用后端上傳接口需要帶鑒權(如 Authorization: Bearer <token>)。token 在小程序里已有,但 H5 拿不到小程序的 storage。
我們采用的方式是:小程序打開 web-view 時,把 token 放在 URL 的 query 里(例如 ?token=xxx&user_id=xxx&api_base=xxx)。
風險控制方式:僅用于這一次上傳流程、token 有時效、全程 HTTPS。如果你們對 token 暴露在 URL 里特別敏感,也可以讓后端為「web-view 上傳」單獨發一次性臨時 token,用一次即廢。
方案架構與數據流
整體可以理解為三塊:小程序列表頁 → web-view 容器頁 → H5 上傳頁,再加大后端上傳接口。
- 小程序列表頁:列表 + 添加文件入口;點擊「從手機文件導入」時跳轉到 web-view 頁面,并帶上 token、user_id、api_base 等參數(通過 web-view 的 URL 傳)。
- web-view 頁面:只負責承載一個全屏 web-view,
src 指向 H5 上傳頁的完整 URL(含上述參數)。 - H5 上傳頁:一個獨立 HTML,內有一個「選擇文件」按鈕(對應
<input type="file">),選完做前端校驗(大小、格式),再用 XHR 以 multipart/form-data 調后端上傳接口,成功后調用 wx.miniProgram.navigateBack() 回到小程序。 - 列表頁 onShow:從 web-view 返回時會再次觸發
onShow,在這里調 refreshList() 拉最新列表即可,無需依賴 postMessage。
這樣用戶路徑就是:添加文件 → 選擇「從手機文件導入」→ 進入 H5 → 選文件 → 上傳 → 自動返回 → 列表已更新。
關鍵實現要點
1. 小程序側:導入方式用 ActionSheet 二選一
在「添加文件」處不直接調 wx.chooseMessageFile,而是先彈出 ActionSheet,再根據用戶選擇走不同分支:
- 從聊天中導入:走原來的
wx.chooseMessageFile 邏輯 - 從手機文件導入:
navigateTo 到 web-view 頁面,并把 H5 地址和參數拼好(H5 根地址用 getH5BaseUrl() 按環境區分,再拼上路徑和 query)。
2. H5 頁:input[type=file] + XHR 上傳
- 用
<input type="file" accept=".pdf,.doc,.docx"> 調起系統文件選擇器(iOS/Android 都會用系統原生選擇器)。 - 在
change 里取 file,做前端校驗:大小(如 ≤10MB)、格式白名單。 - 用
FormData 把 file 塞進去,XHR 的 URL 用 URL 參數里的 api_base 拼出完整上傳地址,請求頭里加 Authorization: Bearer <token>(token 從 URL 參數里取)。 - 上傳成功后調用
wx.miniProgram.navigateBack(),失敗則在當前頁提示錯誤,允許重選重傳。
3. 環境與域名配置
- 為不同環境(開發/體驗/正式)配置兩套:H5 頁面域名(業務域名)、API 域名(服務器域名)。
- 小程序里用
getH5BaseUrl()、getEnvBaseUrl() 之類方法按環境選 base,再拼到 web-view 的 URL 和 api_base 參數里。 - 微信后臺:業務域名里配 H5 所在域名;request / uploadFile 合法域名里配后端 API 域名(若 H5 用 XHR 直連后端,則后端域名要在這里配置)。
4. 安全性簡要說明
- token 通過 HTTPS 的 URL 傳參,僅用于本次上傳,并有時效。
- 文件類型、大小在前端做一次校驗,后端再做一次(如只允許 PDF/DOC/DOCX、大小上限 10MB),避免濫傳。
小結
用 web-view + H5 的 <input type="file"> 來擴展「從手機文件導入」,可以繞過小程序無法調起系統文件選擇器的限制,同時保留「從聊天導入」的原有邏輯。實現時注意:
- 不依賴 postMessage 觸發刷新:用
navigateBack + 列表頁 onShow 刷新列表更穩。 - 業務域名:web-view 的 H5 域名必須配在「業務域名」里,并放好校驗文件。
- H5 與 API 域名分離:通過 URL 參數把
api_base、token 等傳給 H5,便于多環境和后續擴展。 - 簡單場景用獨立 HTML:上傳頁邏輯簡單時,獨立 HTML + 微信 JS-SDK 就夠用,無需上 Vue 全家桶。
- token 傳參:HTTPS + 短期 token 可接受;若有更高安全要求,可改為一次性臨時 token。
如果你也在做小程序里的「從手機本地選文件」能力,希望這篇能少讓你走一點彎路。
轉自https://juejin.cn/post/7612288624199811106
該文章在 2026/3/9 15:19:47 編輯過