十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

微信小程序扫码获取快递单号实现指南

微信小程序扫码获取快递单号实现指南 简介面向微信小程序开发者这份资源演示了扫码二维码获取快递单号的完整实现方案从wx.scanCode扫码授权、二维码内容Base64解码与正则提取到wx.request请求后端、对接快递公司API查询物流状态再到wxml/wxss结果展示与条件渲染环环相扣。压缩包共14个文件以json配置、js逻辑、wxss样式、wxml页面结构为主要类型整体约15KB目录直观适合直接导入微信开发者工具运行比对。目前已有3449人学习浏览关注度较高。借助该工程可系统掌握扫码功能权限声明、二维码数据解析、快递公司接口参数构造、网络请求调试、加载动画与错误重试优化等实践要点还可借鉴敏感信息服务端存储、请求频率限制、Git版本管理及小程序发布审核等工程化思路为独立开发快递查询类小程序提供清晰可复用的模板。资源主体围绕扫码、查询、展示三条链路展开页面结构与逻辑分层简明适合初学者按模块研读也可作为中级开发者快速扩展同类功能的基础。1. 微信小程序扫码二维码获取快递单号扫码结果不等于快递单号快递驿站、公司前台、退货登记这些场景里常见需求是拿微信小程序扫一下快递面单上的二维码直接把快递单号填进录入表单或者发起物流查询。很多人以为核心是调一个扫码 API但真机跑过一圈就会发现扫码返回的 result 往往不是干净的纯数字单号而是带 URL 参数、中文注释甚至被面单平台加密过的字符串。真正要下功夫的是两步从扫码结果里稳定抽出单号以及识别出它是哪家快递公司。下文按“扫码调用 → 格式解析 → 单号校验与物流查询 → 边界处理”这条链路展开适合用原生微信小程序或 uniapp 微信小程序做物流相关功能的开发者照着落地就能得到一套“扫码即得单号”的最小可用方案。2. 扫码 API 选型与最小实现wx.scanCode 和 uni.scanCode2.1 原生微信小程序 wx.scanCode 的参数拆解微信小程序里扫码只有一条官方通道就是wx.scanCode。它不区分扫码枪还是相机统一走摄像头或相册识别识别结果通过回调返回。最容易被忽略的是scanType默认只包含barCode和qrCode但快递面单上除了二维码还有一维条码部分国际面单选用了datamatrix或pdf417默认值会漏掉它们。// pages/scan/scan.js Page({ data: { scanning: false, trackingNo: }, startScan() { if (this.data.scanning) return this.setData({ scanning: true }) wx.scanCode({ onlyFromCamera: false, scanType: [qrCode, barCode, datamatrix, pdf417], success: (res) { const raw res.result || console.log(扫码原始结果, raw) const trackingNo extractTrackingNo(raw) this.setData({ trackingNo }) this.saveRecord(trackingNo) }, fail: (err) { if (err.errMsg err.errMsg.indexOf(cancel) -1) return wx.showToast({ title: 扫码失败, icon: none }) }, complete: () { this.setData({ scanning: false }) } }) } })参数说明onlyFromCamera设为false时允许从相册选图识别面单反光严重时相册兜底很实用设为true则强制走相机适合驿站批量录入场景避免误触相册。scanType显式列出 4 种是因为电子面单区域往往条码和二维码并存只留qrCode会漏掉条码面单。success回调里的res.result是二维码承载的原始字符串res.path只有扫到小程序码时才有值本场景用不到。fail回调里要区分“用户主动取消”和“真失败”取消时弹 toast 会显得很蠢。complete一定会执行所以把scanning锁在这里复位。2.2 uniapp 微信小程序里封装 uni.scanCode如果项目是 uniapp 开发的代码里应该用uni.scanCode它在微信小程序端会自动映射到wx.scanCode在 App 端则走 plus.barcode。这样一套代码可以同时覆盖小程序和 App不用各自维护扫码逻辑。// utils/scanner.js export function scanTrackingCode() { return new Promise((resolve, reject) { if (!uni.scanCode) { reject(new Error(当前环境不支持 uni.scanCode)) return } uni.scanCode({ onlyFromCamera: false, scanType: [qrCode, barCode, datamatrix], autoDecodeCharset: true, // App 端遇到 GBK 内容时自动转码 success: (res) resolve((res.result || ).trim()), fail: (err) reject(err) }) }) }调用侧用 Promise 接住结果便于在页面里串起后续的单号提取和物流查询逻辑import { scanTrackingCode } from /utils/scanner.js async function onScanTap() { try { const raw await scanTrackingCode() const trackingNo extractTrackingNo(raw) if (trackingNo) { this.setData({ trackingNo }) } } catch (err) { // 取消或失败统一在这里处理 } }autoDecodeCharset是 App 端 HBuilderX 3.1.0 才有的参数编译到微信小程序时会被忽略不会报错。这段封装的另一个好处是后续如果要在 H5 端引入 html5-qrcode 或 jsQR改动只发生在scanner.js内部业务页面不用动。2.3 扫码方式的选型相机、相册、扫码枪、长按识别uni.scanCode 的调用方式本质上只有两种相机实时扫以及相册选图识别。实际项目里还会遇到另外两类方式它们和uni.scanCode是两套链路不要混在一起做。第一类是外接蓝牙或 USB 扫码枪在微信小程序里的使用扫码枪的本质是模拟键盘输入扫到条码后会把内容作为一串字符输入到当前聚焦的 input 里末尾带一个回车处理方式是在 input 的bindinput里累积字符bindconfirm或检测换行符时触发查询而不是调扫码 API。第二类是用户在聊天记录或网页里长按识别二维码这是微信客户端自带能力页面代码接收不到结果只能在生成二维码时把单号编译进 URL让扫码后跳转到小程序页面再解析 URL 参数。选型建议纯小程序场景优先相机扫码因为在暗光、反光面单上相机识别率最高相册选图适合人工补录扫码枪适合批量操作场景比如仓库批量入库因为枪扫速度远快于相机对焦长按识别适合把二维码分享出去让别人扫的场景。分清这四种方式才能避免陷入“为什么我接了扫码枪却调不起 uni.scanCode”这种选型误区。3. 快递面单二维码格式解析与快递单号提取3.1 面单上的二维码到底存了什么电子面单的二维码内容并没有统一标准。我梳理过几类常见面单的实际扫码结果大致可以分成四种面单来源常见二维码内容能否直接提取单号顺丰、圆通、中通标准电子面单纯单号或带字母前缀的单号能正则直接抽部分电商平台自定义面单URL 形式query 里带 code 参数能解析 URL 参数国际件面单公司代码加数字校验位混合串能但前缀容易误判仓配一体面单密文串或包裹号不能需要打单平台解密第四种最坑。有些面单上的二维码内容是菜鸟或京东仓配系统的包裹号扫出来是一串带下划线或特殊字符的密文拿它去查物流接口只会返回“无此单”。遇到这类结果正确做法是直接走手动输入兜底不要尝试在本地解密面单平台的加密体系不是前端能逆向的。所以提取单号的函数第一原则是识别不出来就返回空不要硬造一个单号。3.2 用正则从扫码结果里提取快递单号提取单号的函数要覆盖三种输入纯文本单号、URL 里带参数的单号、中文注释夹杂的单号。快递单号最短 10 位左右最长不超过 32 位字符集是字母加数字所以统一用[A-Z0-9]{8,32}作为候选条件。function extractTrackingNo(raw, fallback) { if (!raw) return fallback || let text String(raw).trim() // 1. URL 形式先尝试从 query 参数里取单号 if (/^https?:\/\//i.test(text)) { try { const url new URL(text) const keys [code, num, trackingNo, waybill, no, billno] for (const key of keys) { const value url.searchParams.get(key) if (value /^[A-Z0-9]{8,32}$/i.test(value)) { text value break } } } catch (e) { // 个别非标准 URL 会被 new URL 抛错忽略后走后续正则 } } // 2. 没有协议头的参数形式比如 ?codeYT123456789 let match text.match(/[?](?:code|num|trackingNo|waybill|no)([A-Z0-9]{8,32})/i) if (match) return match[1].toUpperCase() // 3. 中文注释夹杂比如 “快递单号YT123456789 请核对” match text.match(/(?:单号|运单号|tracking[^\d]{0,6}|no[.:\s]{0,3})\s*[:]?\s*([A-Z0-9]{8,32})/i) if (match) return match[1].toUpperCase() // 4. 兜底取连续字母数字串中最长的一段 const candidates text.match(/[A-Z]{0,4}\d{10,20}[A-Z0-9]{0,4}/gi) || [] if (candidates.length) { candidates.sort((a, b) b.length - a.length) return candidates[0].toUpperCase() } return fallback || }逻辑说明第 1、2 步处理 URL 和 query 参数因为很多电商面单把二维码指向查询页单号放在code或num参数里第 3 步处理从剪贴板或 PDF 复制来的中文串第 4 步是兜底面单上可能同时出现箱号、批次号、单号取最长候选串命中率最高。fallback参数是手动输入值用于“扫不出就让用户确认”的场景。需要注意一个经常踩的坑不要盲目把字母O替换成数字0、把I替换成1。快递单号是字母数字混排的比如顺丰单号确实是纯数字但 EMS 单号就是E加数字加CN替换会直接破坏单号。识别错误的高发区应该在提取逻辑里规避而不是在字符层强行纠错。3.3 按单号前缀识别快递公司的映射表拿到单号后下一步是判断它属于哪家快递。这个判断不需要机器学习快递单号的前缀和长度已经提供了强特征单号特征快递公司常见位数查询接口 com 编码SF 开头顺丰速运12-15 位sfYT / YTO 开头圆通速递13-15 位yuantongZT 或 75 开头中通快递12 位zhongtongSTO 或 468/78x 开头申通快递12-15 位shentongYD 或 43x 开头韵达快递13 位yundaJD / JDKA 开头京东物流15 位左右jdJT 开头或纯数字 14 位极兔速递14 位jtexpressE 字母 9 位数字 CNEMS13 位ems前缀识别写成函数很简单但有两个边界要处理。一是前缀冲突比如YT在极少数地区也可能出现在别家面单上所以前端识别结果只作为预选值页面里要给用户一个可以修改快递公司的下拉框。二是单号校验位顺丰、EMS 的单号确实有校验位算法但各家算法不统一前端做强校验的成本远大于收益格式合法、前缀匹配就足够进入查询流程真正的有效性交给物流接口返回结果来判断。4. 快递单号校验与物流查询对接查询 API 的关键参数4.1 先做格式校验再发查询请求扫码结果经过提取后仍然有可能是乱码或者多提取了一段脏数据所以在发起网络请求之前必须做一层格式校验。校验规则不用复杂正则判断字符集和长度即可。function isTrackingNoLike(str) { if (!str || typeof str ! string) return false return /^[A-Z0-9]{8,32}$/.test(str.toUpperCase()) } // 使用示例 const trackingNo extractTrackingNo(raw) if (isTrackingNoLike(trackingNo)) { queryTrack(com, trackingNo) } else { showManualInput(trackingNo) // 进入手动确认流程 }校验通过只代表格式合法不代表这个单号真实存在。查物流接口返回“无此单”时常见原因有三个单号提取时混入了多余字符、快递公司判断错误、以及面单二维码本身是仓配系统的包裹码。格式校验解决前两个问题第三个问题只能靠手动输入兜底。4.2 快递 100 查询接口的请求与签名物流查询服务商里快递 100 用得最多核心接口是poll.kuaidi100.com/poll/query.do。这个接口要求 POST 表单请求参数包括授权码、签名和查询参数。签名算法是MD5(param key customer)再转大写其中param是 JSON 字符串。关键点key 和 customer 绝不能写在小程序前端代码里。小程序打包后代码可以被反编译密钥等于裸奔。常见做法是把查询逻辑放到微信云函数或者自有后端小程序端只拿单号和快递公司编码去请求自己的后端。// 云函数 /functions/queryTrack/index.js const crypto require(crypto) const axios require(axios) exports.main async (event) { const { com, num } event const customer 你的customer编码 const key 你的授权key const param JSON.stringify({ com, num: String(num).trim() }) const sign crypto.createHash(md5) .update(param key customer) .digest(hex) .toUpperCase() const { data } await axios.post( https://poll.kuaidi100.com/poll/query.do, new URLSearchParams({ customer, sign, param }).toString() ) return data }参数说明com是快递公司编码取值参考上一章的映射表num是单号customer是快递 100 控制台里生成的授权码sign是签名拼接时要保证param key customer之间没有换行和空格param必须严格是 JSON 字符串不能直接传对象。返回结果的数据结构里需要关注几个字段status表示请求是否成功state表示快件状态data数组是轨迹列表。状态码约定是 0 在途、1 揽收、2 疑难、3 签收、4 退签、5 派件、6 退回。页面拿到state后可以直接映射成“运输中 / 已签收 / 问题件”这类用户看得懂的标签不需要自己维护复杂的规则。4.3 查单缓存与轮询间隔同一个二维码在短时间内可能被反复扫尤其是驿站场景顾客扫码查完一次过几分钟又扫一次。如果不做缓存每次扫码都打一次快递 100 接口很快会触发频率限制。用内存 Map 做一层 TTL 缓存是最简单的方案。const trackCache new Map() const CACHE_TTL 15 * 60 * 1000 // 15 分钟 function queryWithCache(com, num) { const key ${com}:${num} const hit trackCache.get(key) if (hit Date.now() - hit.ts CACHE_TTL) { return Promise.resolve(hit.data) } return queryTrack(com, num).then((data) { trackCache.set(key, { ts: Date.now(), data }) return data }) }缓存时机需要注意如果查到的是“已签收”或“问题件”可以把 TTL 设置得更长甚至不过期如果查到的是“待揽收”说明包裹刚进系统快递 100 的轨迹可能延迟这时缓存反而会影响刷新。常见做法是给待揽收状态的记录一个较短 TTL比如 5 分钟让用户再次点击时可以重新拉取。轮询动作本身要克制单号在途时每 15 分钟轮询一次足够高频轮询不是查询接口的设计目标。5. 连续扫码防抖、真机验证和降级输入5.1 连续扫码时的防抖与结果覆盖驿站和仓库的实操作业里用户扫码速度极快一个接一个。常见 bug 是上一次的查询弹窗还没关闭下一次扫码结果已经回来直接把上一次还在编辑的数据覆盖了。解决思路是加一个saving状态锁扫码结果先进入队列等上一个保存动作结束后再消费队列。let scanBusy false let pendingResult function handleScan(raw) { const trackingNo extractTrackingNo(raw) if (!trackingNo) return if (this.data.saving) { pendingResult trackingNo wx.showToast({ title: 已暂存稍后处理, icon: none }) return } this.submitTrack(trackingNo) } function afterSave() { if (pendingResult) { const next pendingResult pendingResult this.submitTrack(next) } }这个模式把“扫码”和“保存”解耦扫码永远不阻塞保存永远不丢单。实际项目里还可以把pendingResult改成数组支撑“连扫 10 个面单再统一提交”的批量录入场景。5.2 用真实面单二维码做回归验证微信开发者工具里的扫码是模拟的只能从本地选图片测不出真实相机的对焦、反光和暗光问题。要验证提取得稳不稳建议准备一组固定的测试样张纯数字单号、带字母前缀单号、URL 参数的二维码、中文注释串、加密面单、打印模糊的条码。把extractTrackingNo写成纯函数用 Node 跑一组断言改完每次解析逻辑先回归测试再上真机。真机调试时注意两点一是把wx.setKeepScreenOn({ keepScreenOn: true })打开扫码页长时间亮屏避免用户扫一半熄屏二是低光环境下微信扫码会自动触发闪光灯但用户手动关闭后就回不来可以在页面上加一个手电筒开关调用wx.createCameraContext().setZoom或者提示用户调整环境亮度。这些细节直接影响扫码成功率比优化正则还见效。5.3 扫描失败或加密内容的手动降级扫码结果提取失败、接口返回“无此单”时页面不能只弹一个失败 toast。正确做法是自动进入手动输入模式把扫码得到的原始内容填进输入框让用户看着面单条形码上方的数字手工修正。提取函数里的fallback参数就是为这个场景准备的。面单上数字才是权威二维码是辅助链条设计成“扫码优先、人工可改、接口校验”才能覆盖全部真实情况。单号录入后立即发起查询并缓存结果用户下次进入页面直接展示缓存轨迹这一整套链路才算真正闭环。本文还有配套的精品资源点击获取
返回列表