ARTICLE DETAIL

资讯详情

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

uniapp-x App端二维码与条形码生成实践及常见坑位解析

uniapp-x App端二维码与条形码生成实践及常见坑位解析 1. 项目背景uniapp-x App 端为什么要自己生成二维码和条形码上个月接了个仓储管理 App 的需求要在订单详情页同时显示条形码和二维码用于线下扫码核销。App 是 uniapp-x官方一般写作 uni-app x写的后端一开始说直接给图片结果移动端同事都在吐槽码的内容带着签名参数每次请求都有时效性等接口回来再渲染体验很差到了信号不好的仓库场景页面直接白掉。所以我把二维码、条形码的生成全部放到了 App 客户端自己写了一套组件顺手把踩过的坑都记了下来这篇应该对用 uniapp-x 开发 App 的朋友有参考价值。先说清楚一个问题uniapp-x 和之前的 uni-app 不是同一个东西。它不再依赖 WebView 去渲染页面而是用 UTS 这门语言编译成 Android 和 iOS 原生界面很多 vue 语法还能用但底层 DOM、BOM 那套东西没有了。你在百度上搜“二维码生成 JS 库”大部分能跑但放到 uniapp-x 里就可能直接报错因为很多库底层依赖document.createElement或者canvas的 web 实现。另一个麻烦是对 canvas 认知。uni-app x 有 canvas 能力但它的 canvas 是原生组件API 跟浏览器不完全一样时序和刷新机制也有差别。很多老项目从 uni-app 迁移过来时原来的 tki-qrcode 这类插件换到 uniapp-x 后会出现白屏、导出空白、保存相册失败等问题原因基本都出在这里。所以这次我把生成方案、组件封装、保存导出、性能踩坑一起整理了希望能帮你少走点弯路。1.1 一个典型的业务场景订单码和物料码我这次接的仓库场景是这样的每个订单在生成后需要一个条形码贴在周转箱上方便仓库用扫码枪快速读取还需要一个二维码打印在面单上用户或者工作人员扫码跳转到详情核销页。条形码内容比较短就是订单号二维码内容长一些要带上核销跳转地址和签名参数。页面上有两种交互一个是直接展示供现场扫码另一个是长按保存图片方便导到标签纸上打印。这种需求如果在 H5 里做方案很多前端任意一个 qrcode 库都能搞定。但客户端就不一样了你要考虑的不是“能不能生成”而是生成出来的码在真机上扫描识别的成功率怎么样保存后的图片是否足够清晰列表页几十个订单同时加载会不会卡顿。这些才是 App 开发的真相。1.2 uniapp-x 与传统 WebView 方案的核心差别在 uniapp-x 之前uni-app 打开 App 页面时很多场景其实还是靠 WebView 或者小程序容器来渲染页面所以把浏览器里能跑的那套 js 库塞进来大概率没问题。但 uniapp-x 从渲染层就换了底子页面里的view、text、canvas最终都会对应到系统原生控件。这个改动带来的影响是很多 npm 上看似通用的库你在script里import的时候不报错一调用就崩。原因通常是库内部访问了window、navigator、document这些浏览器对象。uniapp-x 运行环境里根本没有这些全局对象。如果你打算用原生 canvas 画二维码就必须找一个“不依赖 DOM 操作只做矩阵计算”的核心然后在渲染阶段自己绘制。相比之下从插件市场找一个专门适配 uniapp-x 的 uni_modules 组件会更省事这也是我最终采用的方案。2. 生成方案选型后端图片、JS 库还是客户端组件在真正动手前一定要先做一次方案权衡。很多人一上来就找个开源库开始敲代码结果中途踩到平台限制白干好几天。2.1 后端先生成图片为什么我不建议优先考虑让后端生成图片、客户端直接展示是看起来最省事的方式。后端生成二维码本质上就是拿一个现成库把字符串转成 PNG然后返回一个图片地址。客户端image标签直接展示几乎不用写逻辑。但真实项目里这套做法有几个问题一是时效性。像订单详情这种页面码的内容里可能包含 token 或者签名这种签名一般会在短时间内过期。如果客户端缓存了图片或者用户停留在页面时间较长就会出现“图片看着还在但扫进详情页已经过期”的情况。如果你让页面每次回到前台都刷新又要等网络离线场景直接不可用。二是打印导出成本高。仓储业务的标签打印往往需要连续打出几十上百个码如果都靠后端逐个请求打包下载不仅慢还会给服务器凭空增加压力。客户端生成时导出的是一张高清位图这个过程完全本地化速度快很多。三是没法实时编辑。有些定制场景里码的内容需要在客户端本地拼出来例如用户先把商品编号输入文本框再自己设置条码尺寸。如果每次都请求后端交互会非常不顺畅。不排除服务端生成在纯展示类、审计留痕类场景下有它的价值但凡是涉及离线、实时、批量导出客户端生成基本都是绕不过去的。2.2 纯前端 JS 库在 uniapp-x 里的真实可行性我一开始也想复用旧项目里的 qrcode 库在 uniapp-x 里试了一下结果踩了坑。那些生成二维码的库一般分两层第一层是纯算法根据传入字符串算出二维码矩阵点第二层是canvas或者dom的绘制渲染。纯算法那层通常是可以移植的因为只要不碰window、document它在 UTS 环境里也能跑。真正卡住的是第二层。有的库默认调用浏览器 canvas 的fillRect、createImageDatauniapp-x 的 canvas context 不一定实现了这些方法。有的库为了输出图片内部还拿document.createElement(canvas)去创建离屏画布这在 uniapp-x 里直接就没有对应对象。如果你非要自己引入算法库可行路径是把“算矩阵”和“画画面”拆开只用算法部分矩阵出来之后自己用 uniapp-x 的 canvas 去画。但二维码编码算法并不简单里面涉及数据分段、掩码、Reed-Solomon 纠错除非你只想支持纯数字且长度很短的场景否则自己维护算法非常痛苦。2.3 为什么 uni_modules 组件是更稳妥的选择折中方案是直接使用 uni_modules 形式的组件。uni_modules 是 uni-app 时代的插件规范到了 uniapp-x 也有大量组件继续沿用。这类组件的好处是组件源码就在项目目录里你可以直接看到它到底用了哪些 API排查问题只需要打开目录查代码。很多成熟组件已经处理了多端兼容性包括 App-Android、App-iOS、H5以及微信小程序你不需要自己写一堆条件编译。它没有 npm 依赖纯源码放在项目里方便二次修改。对于本项目我优先找的是明确标注支持 uniapp-x、且最近有更新的二维码/条码组件。至于条形码和二维码要不要用同一个组件其实不重要因为两者的底层完全不一样。二维码信息密度高能够承载 URL、中文、签名等复杂数据条形码信息密度低但结构简单扫描速度快仓储扫码枪更认它。3. 核心流程在 uniapp-x App 里接入二维码组件技术选型定了之后接入就快多了。下面我会按实际开发顺序把二维码这块的接入过程写清楚。示例代码以 uniapp-x 和 uni_modules 组件的常见用法为准不同 gitee/插件市场来源的组件属性名可能略有差异但思路是通用的。3.1 先安装并检查组件的最小依赖先把下载好的二维码组件放到uni_modules目录下。如果你使用的是 HBuilderX直接在插件市场页面点击下载它会把组件自动放到当前项目的uni_modules目录。我的建议是不要直接使用“云端依赖”模式而是选择“下载到本地 uni_modules”的版本这样后续排查和改样式都比较方便。安装完先别急着写页面打开组件的readme.md确认两点是否支持你当前编译目标尤其是 App-Android 和 App-iOS。是否依赖了canvas原生节点如果组件内部走的是 webview 转发就不适合 uniapp-x。很多组件版本在 uni-app 上能跑但到了 uniapp-x 会报 “Canvas is not defined”这就是典型的未适配版本。遇到这种情况不要去问组件作者先看更新日期和说明里有没有提“适配 uni-app x”。3.2 二维码组件的最小可用代码假设你的页面结构里需要展示一个二维码并在二维码下方显示订单号Vue 模板大致可以写成这样template view classorder-card view classcode-box lime-qrcode refqrRef :valueqrContent :size192 :margin8 colorDark#000000 colorLight#ffffff readyonQrReady / /view text classorder-no{{ orderNo }}/text /view /template script setup import { ref, computed } from vue const props defineProps({ orderNo: { type: String, required: true }, sign: { type: String, default: } }) // 这里拼二维码内容建议统一放在一个函数里处理 const qrContent computed(() { return https://example.com/verify?orderNo${encodeURIComponent(props.orderNo)}sign${props.sign} }) function onQrReady(e) { // 此时二维码已经绘制到画布上可以做导出或保存 console.log(二维码 ready可以开始导出操作) } /script重点强调一下组件属性名不一定是value有的组件会叫text有的叫content甚至还有叫data的。在接入前先去看组件源码里的props不要凭猜。上面示例里lime-qrcode是我项目里用的一个组件你项目里可能是code-qr、qr-canvas之类把标签名换掉就行整体流程不受影响。二维码size这一项我建议至少给到 160 以上。太小的码在手机屏幕上看没问题但打印到标签纸上会模糊扫码枪稍微离远一点就识别不了。如果显示尺寸只有 100 多在生成图片做导出时也要调整清晰度不能直接用屏幕显示的像素尺寸。3.3 二维码内容里的关键编码细节二维码本身是一个二进制矩阵它支持的数据类型很多但如果你要把 URL 放进去有一个坑特别常见URL 里的参数如果包含中文、空格、特殊符号一定要先做encodeURIComponent。举个例子假如跳转地址是https://example.com/verify?orderNoA1001userName张三如果不编码二维码里存的就是明文。张三两个字在部分扫码 App 的识别结果里可能会乱码因为二维码编码会根据内容选择不同的字符集而手机上扫码后打开浏览器的解析方式并不完全相同。用encodeURIComponent处理之后内容变成%E5%BC%A0%E4%B8%89兼容性最好。另外你还要注意二维码内容的长度。一张二维码能存多少信息取决于容量但内容越长码就越密在小尺寸屏幕上就越难扫。常规做法是不要把一长串 JSON 直接放进去尽量只放一个短编号或者简短地址需要附加参数时让后端通过短链或签名服务处理。4. 条形码生成和一维码制相关的实操细节条形码看起来比二维码简单但真的做起来需要注意的地方一点也不少。下面把常见的条码类型选择和生成经验展开讲。4.1 先搞清楚该用哪种码制条形码并不是只有一种。仓库里最常见的 Code128超市商品上常用 EAN-13物流面单有时候还会出现 Code39、UPC-A 等。在选择组件的时候一定要确认它支持哪些码制否则你可能生成出来的图只是“看起来像条形码”扫码枪根本不认。我这次用的是 Code128。它的优点是支持全部 ASCII 字符长度可长可短信息密度高而且生成规则相对灵活。EAN-13 只能放 13 位数字最后一位还是校验位如果订单号里有字母它就不支持。如果你想兼容未来可能出现的 SKU 场景直接统一用 Code128 是比较省心的选择。条形码内容不要出现中文。虽然 Code128 可以通过字符集转换映射部分字符但实际扫码枪对中文条码的支持参差不齐真机验证成本也高。我们仓库的标签全部用数字和英文字母拼内容宁可多一个编号映射关系也不在条码上直接放中文。4.2 条形码组件的接入代码条形码组件使用时核心要关注几个参数内容、码制、宽度、高度、是否显示文字。我项目里大致这样写的template view classbarcode-wrap lime-barcode :valuebarcodeText formatcode128 :width2 :height72 :showTexttrue backgroundColor#FFFFFF lineColor#000000 readyonBarcodeReady / /view /template script setup import { ref } from vue const props defineProps({ barcodeText: { type: String, default: } }) function onBarcodeReady(e) { console.log(条形码已绘制完成) } /script条形码的width参数非常敏感。这里指的是“每一个最窄单元的像素宽度”不是整个条码的总宽度。如果太窄扫码枪分辨率不够容易把相邻的黑条并在一起识别失败如果太宽整个条码会超过标签打印范围扫到最后缺一段同样失败。通常 2px 到 4px 是一个比较稳的范围具体要看二维码所在介质的打印精度。屏幕显示可以稍微放宽但一旦要导出图片打印我更推荐按打印机的 300 DPI 去提一档导出分辨率。4.3 提升扫码枪识别率的关键设置很多开发同学看到条形码“生成出来”就觉得完事了实际上“能被扫码枪扫出来”才是最终目标。影响扫码枪识别的因素有三个静区、对比度、宽高比。静区就是条形码左右两侧必须保留的空白区域。Code128 标准中静区最小宽度通常是 10 倍最窄单元宽度。有的组件在默认 layout 里会把条形码紧贴父容器的边缘左右一点空白都不留这会导致扫码枪找不到条码的起始位置识别失败。解决办法是在条形码外层容器加上 padding留出至少 10px 以上的空白。对比度就是黑条和白底的关系。很多美观设计要求“配色跟随主题”把条形码线条改成灰色甚至蓝色结果一扫就废。我建议条形码和二维码的线条统一用纯黑背景用纯白不要用透明背景。透明背景在屏幕上看没问题导出成 PNG 或打印时如果设备不支持 alpha 通道背景会变黑或者出现灰色对比度瞬间崩掉。宽高比方面条形码不能太矮。如果高度小于总宽度的 15%很多扫码枪的光束扫描不完整。我在仓储标签里设置的条码高度是 72px配合 2px 条宽整体比例比较稳定。5. 保存图片和列表性能真正拉开体验差距的地方码能画出来只是第一步。在 App 真实使用场景里需要把码保存到相册、上传到后端或者在长列表里展示几十个码性能和用户体验很容易在这里翻车。5.1 如何把二维码或条形码导出并保存到相册如果组件内部已经创建了 canvas保存图片的最终思路无非两种一种是组件直接暴露toTempFilePath方法另一种是你拿到 canvas 节点后自己调用uni.canvasToTempFilePath。在 uniapp-x 里组件如果能帮我们导出尽量用组件封装好的方法最省心。如果组件没有封装代码大致这样function saveQrImage() { const query uni.createSelectorQuery() query.select(#qrCanvas).fields({ node: true, size: true }, (res) { if (!res || !res.node) { return } const canvas res.node uni.canvasToTempFilePath({ canvas, success: (file) { uni.saveImageToPhotosAlbum({ filePath: file.tempFilePath, success: () { uni.showToast({ title: 已保存到相册 }) }, fail: () { uni.showToast({ title: 保存失败请检查相册权限 }) } }) } }) }).exec() }这里有一个常见的时序坑必须等二维码在画布上完整绘制完成后再执行canvasToTempFilePath。很多人在页面onReady里直接调用保存结果导出的图片是全白的因为二维码还没有画完。解决办法是监听组件抛出的ready事件在这个事件回调里再执行保存。如果组件没有 ready 事件你可以在首次渲染后用一个小的延时比如 200 到 300 毫秒再执行导出。别用零延时基础渲染帧还没出来仍然可能白屏。另外保存到相册前一定要处理权限。首次调用uni.saveImageToPhotosAlbum时如果用户拒绝了授权后续再调用会一直失败。如果你发现保存照片失败但用户已经在系统设置里打开了相册权限需要让 App 重新冷启动才能生效这是原生 App 的老问题uniapp-x 一样存在。5.2 列表页多个码一起生成怎么避免卡顿仓储列表页有时候一个屏要显示五六张订单卡片每张卡片里都有二维码和条形码。如果页面一加载就同时渲染所有 canvasAndroid 低端机会明显掉帧甚至白屏。我的做法是做一个“可见区域懒渲染”。列表在滚动时我只让当前处于屏幕范围内的卡片生成码离开视口的卡片直接显示占位图滚动停止后再渲染新进入视口的卡片。这样实现稍微复杂一点但体验提升很大。如果你不想引入复杂的懒加载库也可以用一个更简单的办法对已经生成好的二维码图片做缓存。二维码内容是可预期的比如同一订单号的二维码内容固定那么我们就把第一次生成的结果保存成一个 base64 或本地临时文件地址下次需要展示时直接用image显示不再触发二维码组件重新计算绘制。// 用一个 Map 缓存结果key 是 qr 内容value 是生成后的图片路径 const qrCache new Map() function renderQr(code: string) { if (qrCache.has(code)) { return qrCache.get(code) } // 没有缓存走生成组件生成完成后写入 qrCache }这种缓存策略在无线网络标签打印场景尤其管用。用户选了一百个订单批量打印前面几个生成过之后后面点击“全部导出”时几乎不用再等待。5.3 Android 和 iOS 上的细微差异我在真机调试时发现Android 端某些机型的 canvas 导出图片会出现“背景透明”的情况。二维码的白色区域变成了透明黑点正常。单纯在 App 里看没什么问题但把这张透明 PNG 传给打印机有些打印驱动会直接把透明区域当黑色处理出来的标签有一大块黑底二维码直接作废。解决办法是生成 canvas 时先给整个绘制区域填充白色背景而不是依赖 view 的背景色。二维码矩阵通常只画黑色点白色点其实是透明区域如果画布默认透明最终图片的 alpha 通道就不是全不透明。组件内部的正确做法应该是第一步先用白色fillRect铺满整个画布然后再画黑色模块。iOS 端我碰到的问题集中在导出图片尺寸上。同样一个二维码在 3 倍屏上通过 canvas 导出的 PNG 可能是屏幕显示尺寸的 3 倍大小打印出来很清晰但文件也大了不少批量生成时内存占用会比较高。合理的做法是如果要导出打印指定目标宽高如果只是屏上展示保持系统默认即可。6. 常见问题排查速查表我在开发过程中把典型问题整理成一张表做技术支撑群的时候也经常直接发出去。这里也分享给你现象可能原因解决办法二维码显示为全黑方块颜色参数反了把前景色配成了白色背景色配成了黑色检查 colorDark 和 colorLight 参数确保 Dark 是黑Light 是白二维码能显示但是扫码没反应码的尺寸太小或是内容长度超长导致模块过密调大尺寸减少内容长度检查是否加过 URL 编码白底 PNG 导出后变黑底canvas 没有绘制白色背景透明通道导致打印设备解析异常在绘制码之前先用白色填充整个画布条形码扫码枪扫不出左右静区不足或条宽太细给条码容器加 padding至少 10px把 width 调到 2px 以上条形码内容含中文扫出来乱码Code128/Code39 对中文支持不稳定内容强制使用数字和字母避免中文保存到相册总是失败相册权限未授权或用户曾拒绝过授权引导用户去系统设置打开相册权限再执行保存生成后导出的图是白图画布还没画完就执行了导出监听 ready 事件或在渲染完成回调后再触发导出页面滚动时扫码卡顿多个 canvas 同时渲染性能开销大懒加载只渲染可视区域对已生成结果做缓存同一个码显示多次内容一样但每次生成都重新加载没有缓存组件内部又重新算了一遍用 Map 缓存内容到图片路径的映射这些坑看起来都不大但每一条都有可能让一个看似“已经做完”的功能在真机验证时返工。尤其是条码的渲染时序问题它在模拟器上经常不出现一上 Android 真机就暴露无遗。最后再分享一个小建议码生成完之后不要只在 App 里看着没问题就提交测试。先把它保存成图片用你的手机微信扫一扫再找一把仓库里的扫码枪扫一次条件允许的话用标签机打出来再扫一次。很多问题就是在这种“多此一举”的验证阶段暴露出来的。开发扫码类功能最终评判标准永远是“真实扫描设备能不能一秒识别”而不是“画布上的图案像不像一个码”。这个底色想清楚后面写的代码会踏实很多。
返回列表